OpenCV中的轮廓识别也是需要经常需要用到;识别到轮廓后,再对轮廓区域进行操作,下面先看看我们需要实现的效果:
识别轮廓并绘制
如何识别轮廓?
识别轮廓的关键API其实非常简单,他需要用到FindContours这个函数,他在Imgproc这个类下面。
Imgproc.findContours(Mat image,List<MatOfPoints> contours,Mat hierarchy int mode, int method)
但你不能直接传入3通道的彩色图像。你需要对图像灰度化以后再进行二值化,这样处理以后才能把图像交给上面这个API进行处理!接下来我们一步步对图像进行操作,并把他的轮廓给找到!
所以找轮廓的流程为:
读取图像->转化为灰度图->二值化->寻找轮廓->画出轮廓
原图为下:
需要处理的图1.读取图片到Mat,读取图片转Mat讲过多次了,代码如下:
public RawImage img;
public string IMGNAME = "pca_test1.jpg";
void Start ()
{
var str=Utils.getFilePath(IMGNAME);
//这里是OpenCVForUnity提供的一个功能类,
//他去查询StreamingAssets目录下对应文件
//如果有就返回文件的路径
Debug.Log(str);
if (str != "")
{
Run(str);
}
}
public void Run(string path)
{
var src=Imgcodecs.imread(path);
//读取图像到Mat 第一步
}
2.把Mat转为灰度的图像,并二值化
Mat gray = new Mat();
//创立一个Gray的Mat容器
Imgproc.cvtColor(src, gray,Imgproc.COLOR_BGR2GRAY);
//转化色彩空间为灰度图
Mat bw = new Mat();
//创了一个Bw的Mat容器
Imgproc.threshold(gray, bw, 50, 255, Imgproc.THRESH_BINARY | Imgproc.THRESH_OTSU);
//把图片二值化后装入BW容器
二值化后的图像如上
3.二值化的后的图像,这样我们就能进行找轮廓的操作了。
//这是返回值,他会返回轮廓的各种信息在这个Mat容器里
Mat hierarchy = new Mat();
//这个也是一个返回值,他包含了找到的所有轮廓的点的信息。
List<MatOfPoint> contours = new List<MatOfPoint>();
Imgproc.findContours(bw, contours, hierarchy, Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);
//返回的所有轮廓的点信息都在contours这个列表,
//如果不出意外的话,他返回值应该是7
Debug.Log(contours.Count);
debug的轮廓数
4.下面我们让他把轮廓在图像上表现出来,我们需要用到drawContours这个函数。
//因为上面说contours这个列表里包含每个轮廓的信息
//所以我们要遍历这个列表
for (int i = 0; i < contours.Count; i++) {
//第一个参数是彩色的原图,第二个是列表,
//第三个参数是对应的IDX,指向列表里的第几个轮廓
//第四个参数是色彩new scalar(r,g,b) -0 0 255就是蓝色
//第五个参数是线的粗细thick
Imgproc.drawContours(src, contours, i, new Scalar(0, 0,255), 10);
}
//----------把图像显示在rawImage上的代码--------------
Texture2D tex = new Texture2D(src.width(), src.height());
Utils.matToTexture2D(src, tex);
this.img.texture = tex;
画出来的轮廓
5.看!我们已经成功找到轮廓了。
但是......
如
果
你
想
了
解
更
多
你
可
以
继
续
往
下
看!
比如你说在这张图中外面的轮廓并不是我想要找到的轮廓,这时候你就需要用到imgpro.contourArea()这个函数可以计算轮廓的范围,比如一些过于小或者过于大的轮廓都可以经过这个判断给排除掉。我们只需要加入到for循环中。
for (int i = 0; i < contours.Count; i++) {
//这个获取到轮廓的大小
var area= Imgproc.contourArea(contours[i]);
Debug.Log(area);
//这里的值你可以自己写,根据你自己的图像需求。
if (area > 20000)
{
//如果大于20000就说明过大,不绘制。
continue;
}
Imgproc.drawContours(src, contours, i, new Scalar(0, 0,255), 10);
}
var area= Imgproc.contourArea(contours[i]);
优化以后的轮廓绘制可以帮助你更好的排除一些噪音和不正确的轮廓
同时我们还有一些需求,比如说找到轮廓的中心或者包含轮廓的矩形范围;这样有时候才能更好的对轮廓区域进行处理。
这时候我们需要介绍到下面两个API
boundingRect //画出包含轮廓的矩形范围
miniAreaRect //画出包含轮廓最小的可旋转矩形
我们一个个的说咯:
boudingRect比较简单好理解,他就是传入一个contour的轮廓,他返回一个Rect(这个Rect和Unity的Rect不一样,所以要好好看哦);
//传入每个轮廓得到每个轮廓的矩形范围
var _rect= Imgproc.boundingRect(contours[i]);
//把轮廓绘制到原始的彩色图像上
Imgproc.rectangle(src, _rect, new Scalar(255, 0, 0),3);
绘制出来的矩形区域
你可以看到其实每个矩形区域是有互相重叠的。
你也可以通过rect算出来他的center 并绘制到原始图像上。
var centerpoint = new Point(_rect.x + _rect.width / 2, _rect.y + _rect.height / 2);
Imgproc.circle(src, centerpoint, 5, new Scalar(0, 255, 0), -1);
绘制出来的中心点
下面来看看miniAreaRect()这个API
他返回的一个RotatedRect的参数他不能直接传入contours的轮廓,而是要进行转换成MatOfPoint2f的类型。
var minRect = Imgproc.minAreaRect(new MatOfPoint2f(contours[i].toArray()));
里面包含了center(矩形中心点),boundingRect(外围矩形)
你可以看到RotatedRect里包含很多有用的信息
网友评论