上一个教程: 轮廓:入门
下一个教程: 轮廓属性
目标
在本文中,我们将学习
- 找到轮廓的不同特征,如面积、周长、质心、边界框等
- 您将看到许多与轮廓相关的函数。
1. 矩
图像矩可以帮助您计算一些特征,如物体的质心、物体的面积等。请查看维基百科页面上的图像矩
函数cv.moments() 给出了所有计算出的矩值的字典。见下文
import numpy as np
import cv2 as cv
img =
cv.imread(
'star.jpg', cv.IMREAD_GRAYSCALE)
assert img is not None, "无法读取文件,请检查 os.path.exists()"
cnt = contours[0]
print( M )
CV_EXPORTS_W Mat imread(const String &filename, int flags=IMREAD_COLOR_BGR)
从文件加载图像。
double threshold(InputArray src, OutputArray dst, double thresh, double maxval, int type)
对每个数组元素应用固定级别的阈值。
Moments moments(InputArray array, bool binaryImage=false)
计算多边形或栅格化形状的所有三阶矩。
void findContours(InputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method, Point offset=Point())
在二值图像中查找轮廓。
从这些矩中,您可以提取有用的数据,如面积、质心等。质心由以下关系式给出,\(C_x = \frac{M_{10}}{M_{00}}\) 和 \(C_y = \frac{M_{01}}{M_{00}}\)。这可以按如下方式完成
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
2. 轮廓面积
轮廓面积由函数cv.contourArea()或矩,M['m00']给出。
double contourArea(InputArray contour, bool oriented=false)
Calculates a contour area.
3. 轮廓周长
也称为弧长。可以使用cv.arcLength()函数找到。第二个参数指定形状是闭合轮廓(如果传递 True),还是仅为曲线。
double arcLength(InputArray curve, bool closed)
计算轮廓周长或曲线长度。
4. 轮廓近似
它将轮廓形状近似为另一个顶点较少的形状,具体取决于我们指定的精度。它是Douglas-Peucker 算法的实现。查看维基百科页面以获取算法和演示。
要理解这一点,假设您正在尝试在图像中找到一个正方形,但由于图像中的某些问题,您没有得到一个完美的正方形,而是一个“糟糕的形状”(如下面第一张图像所示)。现在您可以使用此函数来近似该形状。在此,第二个参数称为 epsilon,它是从轮廓到近似轮廓的最大距离。它是一个精度参数。需要明智地选择 epsilon 才能获得正确的输出。
void approxPolyDP(InputArray curve, OutputArray approxCurve, double epsilon, bool closed)
以指定精度逼近多边形曲线。
下面,在第二张图像中,绿线显示了 epsilon = 弧长的 10% 的近似曲线。第三张图像显示了 epsilon = 弧长的 1% 的相同曲线。第三个参数指定曲线是否闭合。
image
5. 凸包
凸包看起来类似于轮廓近似,但并非如此(在某些情况下,两者可能会提供相同的结果)。在这里,cv.convexHull()函数检查曲线的凸度缺陷并对其进行校正。一般来说,凸曲线是指总是向外凸出的曲线,或者至少是平坦的曲线。如果它向内凸出,则称为凸度缺陷。例如,检查下面的手部图像。红线显示了手的凸包。双向箭头标记显示了凸度缺陷,它是船体与轮廓的局部最大偏差。
image
关于它的语法还有一些需要讨论的地方
hull =
cv.convexHull(points[, hull[, clockwise[, returnPoints]]])
void convexHull(InputArray points, OutputArray hull, bool clockwise=false, bool returnPoints=true)
Finds the convex hull of a point set.
参数详细信息
- points是我们传入的轮廓。
- hull是输出,通常我们避免它。
- clockwise:方向标志。如果为 True,则输出凸包按顺时针方向定向。否则,它将逆时针方向定向。
- returnPoints:默认情况下为 True。然后它返回船体点的坐标。如果为 False,则返回与船体点对应的轮廓点的索引。
因此,要获得如上图所示的凸包,以下就足够了
但是,如果您想找到凸度缺陷,则需要传递 returnPoints = False。为了理解它,我们将采用上面的矩形图像。首先我找到了它的轮廓作为 cnt。现在我发现它的凸包 returnPoints = True,我得到以下值:[[[234 202]],[[51 202]],[[51 79]],[[234 79]]],它们是矩形的四个角点。现在,如果使用 returnPoints = False 执行相同的操作,我将得到以下结果:[[129],[67],[0],[142]]。这些是轮廓中对应点的索引。例如,检查第一个值:cnt[129] = [[234, 202]],它与第一个结果相同(其他结果依此类推)。
当我们讨论凸度缺陷时,您将再次看到它。
6. 检查凸度
有一个函数可以检查曲线是否为凸曲线,cv.isContourConvex()。它只是返回 True 或 False。没什么大不了的。
bool isContourConvex(InputArray contour)
测试轮廓的凸性。
7. 边界矩形
有两种类型的边界矩形。
7.a. 直立边界矩形
它是一个直立的矩形,它不考虑物体的旋转。因此,边界矩形的面积不会是最小的。它由函数cv.boundingRect()找到。
令 (x,y) 为矩形的左上角坐标,(w,h) 为其宽度和高度。
void rectangle(InputOutputArray img, Point pt1, Point pt2, const Scalar &color, int thickness=1, int lineType=LINE_8, int shift=0)
绘制一个简单、粗或填充的矩形。
Rect boundingRect(InputArray array)
计算点集或灰度图像的非零像素的直立边界矩形。
7.b. 旋转矩形
这里,边界矩形以最小面积绘制,因此它也考虑了旋转。使用的函数是cv.minAreaRect()。它返回一个 Box2D 结构,其中包含以下详细信息 - (中心 (x,y)、(宽度、高度)、旋转角度)。但是要绘制此矩形,我们需要矩形的 4 个角。它由函数cv.boxPoints()获得
box = np.int0(box)
void drawContours(InputOutputArray image, InputArrayOfArrays contours, int contourIdx, const Scalar &color, int thickness=1, int lineType=LINE_8, InputArray hierarchy=noArray(), int maxLevel=INT_MAX, Point offset=Point())
绘制轮廓线或填充轮廓。
RotatedRect minAreaRect(InputArray points)
查找包围输入 2D 点集的最小面积的旋转矩形。
void boxPoints(RotatedRect box, OutputArray points)
查找旋转矩形的四个顶点。有助于绘制旋转矩形。
两个矩形都显示在单个图像中。绿色矩形显示了正常的边界矩形。红色矩形是旋转矩形。
image
8. 最小外接圆
接下来,我们使用函数cv.minEnclosingCircle()查找物体的外接圆。它是一个完全覆盖物体且面积最小的圆。
center = (int(x),int(y))
radius = int(radius)
void circle(InputOutputArray img, Point center, int radius, const Scalar &color, int thickness=1, int lineType=LINE_8, int shift=0)
Draws a circle.
void minEnclosingCircle(InputArray points, Point2f ¢er, float &radius)
查找包含二维点集的最小面积圆。
9. 拟合椭圆
下一个是将椭圆拟合到物体。它返回椭圆内接的旋转矩形。
void ellipse(InputOutputArray img, Point center, Size axes, double angle, double startAngle, double endAngle, const Scalar &color, int thickness=1, int lineType=LINE_8, int shift=0)
绘制简单或粗椭圆弧或填充椭圆扇区。
RotatedRect fitEllipse(InputArray points)
围绕一组 2D 点拟合椭圆。
10. 拟合线
同样,我们可以将线拟合到一组点。下面的图像包含一组白点。我们可以将直线近似于它。
rows,cols = img.shape[:2]
[vx,vy,x,y] =
cv.fitLine(cnt, cv.DIST_L2,0,0.01,0.01)
lefty = int((-x*vy/vx) + y)
righty = int(((cols-x)*vy/vx)+y)
cv.line(img,(cols-1,righty),(0,lefty),(0,255,0),2)
void line(InputOutputArray img, Point pt1, Point pt2, const Scalar &color, int thickness=1, int lineType=LINE_8, int shift=0)
绘制连接两点的线段。
void fitLine(InputArray points, OutputArray line, int distType, double param, double reps, double aeps)
将直线拟合到 2D 或 3D 点集。