上一教程: 轮廓:入门
下一教程: 轮廓属性
目标
在本文中,我们将学习
- 查找轮廓的不同特征,如面积、周长、质心、边界框等
- 您将看到大量与轮廓相关的函数。
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)
从文件加载图像。
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)
计算轮廓面积。
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)
以指定的精度逼近多边形曲线(s)。
下面,在第二个图像中,绿线显示了 epsilon = 弧长的 10% 的逼近曲线。第三个图像显示了 epsilon = 弧长的 1% 的情况。第三个参数指定曲线是封闭的还是开放的。
图像
5. 凸包
凸包看起来类似于轮廓逼近,但它不是(在某些情况下,两者可能提供相同的结果)。在这里,cv.convexHull() 函数检查曲线的凸性缺陷并对其进行校正。一般来说,凸曲线是始终向外凸出或至少是平坦的曲线。如果它向内凸出,则称为凸性缺陷。例如,查看下面的手部图像。红线显示了手的凸包。双箭头标记显示凸性缺陷,它们是外壳与轮廓的局部最大偏差。
图像
关于其语法,有一些细节需要讨论
hull =
cv.convexHull(points[, hull[, clockwise[, returnPoints]]])
void convexHull(InputArray points, OutputArray hull, bool clockwise=false, bool returnPoints=true)
查找点集的凸包。
参数详情
- 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), (宽度, 高度), 旋转角度)。但要绘制此矩形,我们需要矩形的四个角点。它由函数 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)
查找旋转矩形的四个顶点。用于绘制旋转矩形。
两个矩形都在一张图像中显示。绿色矩形显示正常的边界矩形。红色矩形是旋转矩形。
图像
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)
绘制一个圆。
void minEnclosingCircle(InputArray points, Point2f ¢er, float &radius)
找到一个包含 2D 点集的最小面积圆。
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 点集。
额外资源
练习