我们在前面的章节里讲过边缘检测,本章所说的轮廓是基于边缘检测的。我们之前所做的边缘检测,结果只是基于像素的,而很多时候,我们可能需要对轮廓进行一些几何操作,例如分析区域是否连通,求出轮廓的凸包,判断一个点是不是在轮廓内,等等。我们先来看两种生成轮廓的方法,再看对轮廓的一些应用。

一、从边缘得到轮廓

虽然轮廓似乎就是边缘,但实际上,OpenCV只能从二值图得到轮廓,边缘被看成是非常窄的区域(宽1个像素)。所以,从边缘得到轮廓时,会出现两条轮廓基本相同,它们是边缘的内外。

示例代码如下:

Mat src = new Mat(img_circle, ImreadModes.Grayscale);

Mat r1 = new Mat();
Cv2.Canny(src, r1, 40, 120, 3);

Mat r2 = new Mat();
Cv2.FindContours(r1, out Mat[] contours, r2, RetrievalModes.List, ContourApproximationModes.ApproxNone);

Scalar[] colors = new Scalar[] {
    new Scalar(255,0,0),
    new Scalar(0,255,0),
    new Scalar(0,0,255),
    new Scalar(255,255,0),
    new Scalar(255,0,255),
    new Scalar(0,255,255),
    new Scalar(180,120,0),
    new Scalar(0,120,180),
    new Scalar(120,0,180),
    new Scalar(60,180,20),
    new Scalar(180,120,5),
    new Scalar(25,180,255)
};

Mat r3 = new Mat(src.Rows, src.Cols, MatType.CV_8UC3);
for (int i = 0; i < contours.Length; i++)
{
    Cv2.DrawContours(r3, contours, i, colors[i], 1, LineTypes.AntiAlias);
}

r3.SaveImage(img_result);

效果如下:

opencv 区域 找图 opencvsharp获取图像区域_opencv 区域 找图

二、从二值图得到轮廓

二值图得到轮廓省了边缘检测一步,示例代码如下:

Mat src = new Mat(img_circle, ImreadModes.Grayscale);

Mat r2 = new Mat();
Cv2.FindContours(src, out Mat[] contours, r2, RetrievalModes.CComp, ContourApproximationModes.ApproxNone);

Scalar[] colors = new Scalar[] {
    new Scalar(255,0,0),
    new Scalar(0,255,0),
    new Scalar(0,0,255),
    new Scalar(255,255,0),
    new Scalar(255,0,255),
    new Scalar(0,255,255),
    new Scalar(180,120,0),
    new Scalar(0,120,180),
    new Scalar(120,0,180),
    new Scalar(60,180,20),
    new Scalar(180,120,5),
    new Scalar(25,180,255)
};

Mat r3 = new Mat(src.Rows, src.Cols, MatType.CV_8UC3);
for (int i = 0; i < contours.Length; i++)
{
    Cv2.DrawContours(r3, contours, i, colors[i], 1, LineTypes.AntiAlias);
}

r3.SaveImage(img_result);

效果如下:

opencv 区域 找图 opencvsharp获取图像区域_opencv 区域 找图_02

三、多边形逼近

多边形逼近的目的是减少轮廓的点数,但看起来轮廓形状差不多。

示例代码如下:

Mat src = new Mat(img_circle, ImreadModes.Grayscale);

Mat r2 = new Mat();
Cv2.FindContours(src, out Mat[] contours, r2, RetrievalModes.External, ContourApproximationModes.ApproxNone);

Mat r3 = new Mat();
Cv2.ApproxPolyDP(contours[0], r3, 5, true);

Mat result = new Mat(src.Rows, src.Cols, MatType.CV_8UC3);
Cv2.DrawContours(result, new Mat[] { r3 }, 0, new Scalar(0, 0, 255), 1, LineTypes.AntiAlias);

result.SaveImage(img_result);

效果如下:

opencv 区域 找图 opencvsharp获取图像区域_数字图像处理_03

原来的轮廓点数为1828,而多边形逼近的结果是53个点。

四、轮廓长度

简单易懂,就是测量轮廓的像素长度,代码如下:

Mat src = new Mat(img_circle, ImreadModes.Grayscale);

Mat r2 = new Mat();
Cv2.FindContours(src, out Mat[] contours, r2, RetrievalModes.External, ContourApproximationModes.ApproxNone);

double length = Cv2.ArcLength(contours[0], true);

五、矩形包围框

一个刚好把轮廓包围起来的矩形框,代码如下:

Mat src = new Mat(img_circle, ImreadModes.Grayscale);

Mat r2 = new Mat();
Cv2.FindContours(src, out Mat[] contours, r2, RetrievalModes.External, ContourApproximationModes.ApproxNone);

OpenCvSharp.Rect rect = Cv2.BoundingRect(contours[0]);

Mat result = new Mat(src.Rows, src.Cols, MatType.CV_8UC3);
Cv2.DrawContours(result, contours, 0, new Scalar(0, 0, 255), 1, LineTypes.AntiAlias);
Cv2.Rectangle(result, rect, new Scalar(0, 255, 0), 1, LineTypes.AntiAlias);

result.SaveImage(img_result);

效果如下:

opencv 区域 找图 opencvsharp获取图像区域_opencv 区域 找图_04

六、最小包围矩形

我们在上一点提到的矩形,是平行于x,y轴的。如果矩形能够倾斜,或许存在面积更小的矩形,把轮廓包围起来。代码如下:

Mat src = new Mat(img_circle, ImreadModes.Grayscale);

Mat r2 = new Mat();
Cv2.FindContours(src, out Mat[] contours, r2, RetrievalModes.External, ContourApproximationModes.ApproxNone);

RotatedRect rect = Cv2.MinAreaRect(contours[0]);
OpenCvSharp.Point[] ps = new OpenCvSharp.Point[4];
for (int i = 0; i < 4; i++)
{
    ps[i] = new OpenCvSharp.Point(rect.Points()[i].X, rect.Points()[i].Y);
}

Mat result = new Mat(src.Rows, src.Cols, MatType.CV_8UC3);
Cv2.DrawContours(result, contours, 0, new Scalar(0, 0, 255), 1, LineTypes.AntiAlias);
Cv2.Polylines(result, new OpenCvSharp.Point[][] { ps }, true, new Scalar(0, 255, 0), 1, LineTypes.AntiAlias);

result.SaveImage(img_result);

效果如下:

opencv 区域 找图 opencvsharp获取图像区域_OpenCvSharp_05

七、最小包围圆

把轮廓包围起来的面积最小的圆。示例代码如下:

Mat src = new Mat(img_circle, ImreadModes.Grayscale);

Mat r2 = new Mat();
Cv2.FindContours(src, out Mat[] contours, r2, RetrievalModes.External, ContourApproximationModes.ApproxNone);

Cv2.MinEnclosingCircle(contours[0], out Point2f center, out float radius);

Mat result = new Mat(src.Rows, src.Cols, MatType.CV_8UC3);
Cv2.DrawContours(result, contours, 0, new Scalar(0, 0, 255), 1, LineTypes.AntiAlias);
Cv2.Circle(result, new OpenCvSharp.Point(center.X, center.Y), (int)radius, new Scalar(0, 255, 0), 1, LineTypes.AntiAlias);

result.SaveImage(img_result);

效果如下:

opencv 区域 找图 opencvsharp获取图像区域_OpenCvSharp_06

八、凸包

凸包是一个把轮廓包围起来的凸多边形。跟多边形逼近的一个区别是,这是凸的。示例代码如下:

Mat src = new Mat(img_circle, ImreadModes.Grayscale);

Mat r2 = new Mat();
Cv2.FindContours(src, out Mat[] contours, r2, RetrievalModes.External, ContourApproximationModes.ApproxNone);

Mat r3 = new Mat();
Cv2.ConvexHull(contours[0], r3);

Mat result = new Mat(src.Rows, src.Cols, MatType.CV_8UC3);
Cv2.DrawContours(result, contours, 0, new Scalar(0, 0, 255), 1, LineTypes.AntiAlias);
Cv2.DrawContours(result, new Mat[] { r3 }, 0, new Scalar(0, 255, 0), 1, LineTypes.AntiAlias);

result.SaveImage(img_result);

效果如下:

opencv 区域 找图 opencvsharp获取图像区域_OpenCV_07

九、Hu矩匹配

我们在直方图一章里讲过,可以用直方图分布来比较两个图像。其实,用轮廓的形状,也是匹配图像的两种方法。Hu矩是两个轮廓差异的表征,越小表示越相似。

示例代码如下:

Mat src = new Mat(img_letter, ImreadModes.Grayscale);

Mat r2 = new Mat();
Cv2.FindContours(src, out Mat[] contours, r2, RetrievalModes.External, ContourApproximationModes.ApproxNone);

Mat r3 = new Mat(src.Rows, src.Cols, MatType.CV_8UC3);
for (int i = 0; i < contours.Length; i++)
{
    Cv2.DrawContours(r3, contours, i, new Scalar(255, 255, 255));
}

r3.SaveImage(img_result);

double m1 = Cv2.MatchShapes(contours[0], contours[5], ShapeMatchModes.I3);
double m2 = Cv2.MatchShapes(contours[1], contours[5], ShapeMatchModes.I3);
double m3 = Cv2.MatchShapes(contours[2], contours[5], ShapeMatchModes.I3);
double m4 = Cv2.MatchShapes(contours[3], contours[5], ShapeMatchModes.I3);
double m5 = Cv2.MatchShapes(contours[4], contours[5], ShapeMatchModes.I3);

我们所采用的测试图像为:

opencv 区域 找图 opencvsharp获取图像区域_OpenCV_08

所有字符跟左上角的8进行比较,结果为(数值越小形状越接近):

符号

差异结果

B

0.092

C

3.742

倾斜的8

0.016

N

0.904

6

0.673