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);
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
他不能直接传入contours的轮廓,而是要进行转换成MatOfPoint2f的类型。
var minRect = Imgproc.minAreaRect(new MatOfPoint2f(contours[i].toArray()));
里面包含了center(矩形中心点),boundingRect(外围矩形)