目录
[1. 轮廓面积](#1. 轮廓面积)
[2. 轮廓周长](#2. 轮廓周长)
[3. 轮廓中心与质心](#3. 轮廓中心与质心)
[1. 边界矩形](#1. 边界矩形)
[2. 最小外接圆](#2. 最小外接圆)
[3. 拟合椭圆](#3. 拟合椭圆)
[4. 直线拟合](#4. 直线拟合)
一、轮廓特征分析的基本概念
轮廓特征分析是计算机视觉中的重要任务,通过分析轮廓的几何特征,可以实现目标识别、形状分类、尺寸测量等功能。OpenCV提供了丰富的函数来提取和分析轮廓的各种特征,包括面积、周长、中心坐标、边界框、最小外接圆等。
常见的轮廓特征
几何特征:面积、周长、中心坐标、质心
形状特征:边界框、最小外接圆、最小外接矩形、拟合椭圆
矩特征:零阶矩、一阶矩、二阶矩等
形状描述符:圆形度、矩形度、宽高比等
二、几何特征计算
1. 轮廓面积
轮廓面积是指轮廓所包围的像素点的数量,在OpenCV中使用`cv2.contourArea()`函数计算:
//python
python
import cv2
import numpy as np
from matplotlib import pyplot as plt
#读取图像
img = cv2.imread('shapes.jpg')
#转换为灰度图像
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#阈值分割
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
#检测轮廓
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
#计算并绘制轮廓面积
img_area = img.copy()
for contour in contours:
#计算轮廓面积
area = cv2.contourArea(contour)
#过滤小轮廓
if area < 100:
continue
#计算轮廓中心
M = cv2.moments(contour)
cx = int(M['m10'] / M['m00'])
cy = int(M['m01'] / M['m00'])
#绘制轮廓
cv2.drawContours(img_area, [contour], 1, (0, 255, 0), 2)
#在轮廓中心显示面积
cv2.putText(img_area, f"Area: {int(area)}", (cx, cy), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1)
#显示结果
plt.figure(figsize=(10, 5))
plt.subplot(121), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)), plt.title('原图')
plt.subplot(122), plt.imshow(cv2.cvtColor(img_area, cv2.COLOR_BGR2RGB)), plt.title('轮廓面积')
plt.tight_layout()
plt.show()
2. 轮廓周长
轮廓周长是指轮廓的长度,也称为弧长,在OpenCV中使用cv2.arcLength()函数计算:
//python
python
import cv2
import numpy as np
from matplotlib import pyplot as plt
#读取图像
img = cv2.imread('shapes.jpg')
#转换为灰度图像
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#阈值分割
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
#检测轮廓
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
#计算并绘制轮廓周长
img_perimeter = img.copy()
for contour in contours:
#计算轮廓周长,第二个参数指定轮廓是否闭合
perimeter = cv2.arcLength(contour, True)
#过滤小轮廓
if cv2.contourArea(contour) < 100:
continue
#计算轮廓中心
M = cv2.moments(contour)
cx = int(M['m10'] / M['m00'])
cy = int(M['m01'] / M['m00'])
#绘制轮廓
cv2.drawContours(img_perimeter, [contour], 1, (0, 255, 0), 2)
#在轮廓中心显示周长
cv2.putText(img_perimeter, f"Peri: {int(perimeter)}", (cx, cy), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1)
#显示结果
plt.figure(figsize=(10, 5))
plt.subplot(121), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)), plt.title('原图')
plt.subplot(122), plt.imshow(cv2.cvtColor(img_perimeter, cv2.COLOR_BGR2RGB)), plt.title('轮廓周长')
plt.tight_layout()
plt.show()
3. 轮廓中心与质心
轮廓的中心(也称为质心)可以通过轮廓的矩来计算。在OpenCV中,cv2.moments()函数用于计算轮廓的矩:
//python
python
import cv2
import numpy as np
from matplotlib import pyplot as plt
#读取图像
img = cv2.imread('shapes.jpg')
#转换为灰度图像
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#阈值分割
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
#检测轮廓
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
#计算并绘制轮廓中心
img_centroid = img.copy()
for contour in contours:
#计算轮廓矩
M = cv2.moments(contour)
#过滤小轮廓
if M['m00'] < 100:
continue
#计算质心坐标
cx = int(M['m10'] / M['m00'])
cy = int(M['m01'] / M['m00'])
#绘制轮廓
cv2.drawContours(img_centroid, [contour], 1, (0, 255, 0), 2)
#绘制质心
cv2.circle(img_centroid, (cx, cy), 5, (0, 0, 255), 1)
cv2.putText(img_centroid, f"({cx},{cy})", (cx + 10, cy), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1)
#显示结果
plt.figure(figsize=(10, 5))
plt.subplot(121), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)), plt.title('原图')
plt.subplot(122), plt.imshow(cv2.cvtColor(img_centroid, cv2.COLOR_BGR2RGB)), plt.title('轮廓质心')
plt.tight_layout()
plt.show()
三、形状拟合
1. 边界矩形
边界矩形是指能够完全包围轮廓的最小矩形,在OpenCV中有两种边界矩形:
直立边界矩形:不考虑轮廓的旋转,始终保持直立
旋转边界矩形:考虑轮廓的旋转,是面积最小的边界矩形
直立边界矩形
使用cv2.boundingRect()函数计算:
//python
python
import cv2
import numpy as np
from matplotlib import pyplot as plt
#读取图像
img = cv2.imread('shapes.jpg')
#转换为灰度图像
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#阈值分割
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
#检测轮廓
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
#绘制直立边界矩形
img_bounding = img.copy()
for contour in contours:
#计算边界矩形
x, y, w, h = cv2.boundingRect(contour)
#过滤小轮廓
if w h < 100:
continue
#绘制轮廓
cv2.drawContours(img_bounding, [contour], 1, (0, 255, 0), 2)
#绘制边界矩形
cv2.rectangle(img_bounding, (x, y), (x + w, y + h), (0, 0, 255), 2)
#显示结果
plt.figure(figsize=(10, 5))
plt.subplot(121), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)), plt.title('原图')
plt.subplot(122), plt.imshow(cv2.cvtColor(img_bounding, cv2.COLOR_BGR2RGB)), plt.title('直立边界矩形')
plt.tight_layout()
plt.show()
旋转边界矩形
使用cv2.minAreaRect()函数计算:
//python
python
import cv2
import numpy as np
from matplotlib import pyplot as plt
#读取图像
img = cv2.imread('shapes.jpg')
#转换为灰度图像
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#阈值分割
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
#检测轮廓
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
#绘制旋转边界矩形
img_rotated = img.copy()
for contour in contours:
#计算旋转边界矩形
rect = cv2.minAreaRect(contour)
box = cv2.boxPoints(rect) #获取矩形的四个顶点
box = np.int0(box) #转换为整数坐标
#过滤小轮廓
area = cv2.contourArea(contour)
if area < 100:
continue
#绘制轮廓
cv2.drawContours(img_rotated, [contour], 1, (0, 255, 0), 2)
#绘制旋转边界矩形
cv2.drawContours(img_rotated, [box], 0, (0, 0, 255), 2)
#显示结果
plt.figure(figsize=(10, 5))
plt.subplot(121), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)), plt.title('原图')
plt.subplot(122), plt.imshow(cv2.cvtColor(img_rotated, cv2.COLOR_BGR2RGB)), plt.title('旋转边界矩形')
plt.tight_layout()
plt.show()
2. 最小外接圆
最小外接圆是指能够完全包围轮廓的最小圆形,使用cv2.minEnclosingCircle()函数计算:
//python
python
import cv2
import numpy as np
from matplotlib import pyplot as plt
读取图像
img = cv2.imread('shapes.jpg')
转换为灰度图像
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
阈值分割
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
检测轮廓
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
绘制最小外接圆
img_circle = img.copy()
for contour in contours:
计算最小外接圆
(x, y), radius = cv2.minEnclosingCircle(contour)
center = (int(x), int(y))
radius = int(radius)
过滤小轮廓
if radius < 10:
continue
绘制轮廓
cv2.drawContours(img_circle, [contour], 1, (0, 255, 0), 2)
绘制最小外接圆
cv2.circle(img_circle, center, radius, (0, 0, 255), 2)
显示结果
plt.figure(figsize=(10, 5))
plt.subplot(121), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)), plt.title('原图')
plt.subplot(122), plt.imshow(cv2.cvtColor(img_circle, cv2.COLOR_BGR2RGB)), plt.title('最小外接圆')
plt.tight_layout()
plt.show()
3. 拟合椭圆
使用cv2.fitEllipse()函数可以拟合轮廓为椭圆:
//python
python
import cv2
import numpy as np
from matplotlib import pyplot as plt
#读取图像
img = cv2.imread('shapes.jpg')
#转换为灰度图像
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#阈值分割
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
#检测轮廓
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
#绘制拟合椭圆
img_ellipse = img.copy()
for contour in contours:
#拟合椭圆需要至少5个点
if len(contour) < 5:
continue
#过滤小轮廓
area = cv2.contourArea(contour)
if area < 100:
continue
#拟合椭圆
ellipse = cv2.fitEllipse(contour)
#绘制轮廓
cv2.drawContours(img_ellipse, [contour], 1, (0, 255, 0), 2)
#绘制拟合椭圆
cv2.ellipse(img_ellipse, ellipse, (0, 0, 255), 2)
#显示结果
plt.figure(figsize=(10, 5))
plt.subplot(121), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)), plt.title('原图')
plt.subplot(122), plt.imshow(cv2.cvtColor(img_ellipse, cv2.COLOR_BGR2RGB)), plt.title('拟合椭圆')
plt.tight_layout()
plt.show()
4. 直线拟合
使用cv2.fitLine()函数可以拟合轮廓为直线:
//python
python
import cv2
import numpy as np
from matplotlib import pyplot as plt
#读取图像
img = cv2.imread('line.jpg')
#转换为灰度图像
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#阈值分割
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
#检测轮廓
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
#绘制拟合直线
img_line = img.copy()
for contour in contours:
#拟合直线
[vx, vy, x, y] = cv2.fitLine(contour, cv2.DIST_L2, 0, 0.01, 0.01)
#计算直线端点
lefty = int((x vy / vx) + y)
righty = int(((img.shape[1] x) vy / vx) + y)
#绘制轮廓
cv2.drawContours(img_line, [contour], 1, (0, 255, 0), 2)
#绘制拟合直线
cv2.line(img_line, (img.shape[1] 1, righty), (0, lefty), (0, 0, 255), 2)
#显示结果
plt.figure(figsize=(10, 5))
plt.subplot(121), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)), plt.title('原图')
plt.subplot(122), plt.imshow(cv2.cvtColor(img_line, cv2.COLOR_BGR2RGB)), plt.title('拟合直线')
plt.tight_layout()
plt.show()
四、矩特征
矩是描述图像形状的重要特征,在OpenCV中使用cv2.moments()函数计算轮廓的矩。矩可以分为:
零阶矩(m00):与轮廓面积相关
一阶矩(m10, m01):与质心位置相关
二阶矩(m20, m11, m02):与图像的旋转、缩放等特性相关
三阶矩(m30, m21, m12, m03):与图像的扭曲相关
矩的计算与应用
//python
python
import cv2
import numpy as np
from matplotlib import pyplot as plt
#读取图像
img = cv2.imread('shapes.jpg')
#转换为灰度图像
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#阈值分割
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
#检测轮廓
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
#计算矩特征
img_moments = img.copy()
for contour in contours:
#计算矩
M = cv2.moments(contour)
#过滤小轮廓
if M['m00'] < 100:
continue
#计算质心
cx = int(M['m10'] / M['m00'])
cy = int(M['m01'] / M['m00'])
#计算面积(与cv2.contourArea()结果相同)
area = M['m00']
#计算中心矩(归一化的二阶矩)
if M['m00'] != 0:
mu20 = M['m20'] / M['m00']
mu11 = M['m11'] / M['m00']
mu02 = M['m02'] / M['m00']
#计算 Hu矩(具有旋转、缩放、平移不变性)
huMoments = cv2.HuMoments(M)
#对Hu矩取对数并取绝对值,便于比较
for i in range(7):
huMoments[i] = 1 np.sign(huMoments[i]) np.log10(abs(huMoments[i]))
#绘制轮廓和质心
cv2.drawContours(img_moments, [contour], 1, (0, 255, 0), 2)
cv2.circle(img_moments, (cx, cy), 5, (0, 0, 255), 1)
#显示结果
plt.figure(figsize=(10, 5))
plt.subplot(121), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)), plt.title('原图')
plt.subplot(122), plt.imshow(cv2.cvtColor(img_moments, cv2.COLOR_BGR2RGB)), plt.title('矩特征分析')
plt.tight_layout()
plt.show()
五、实际应用案例
- 形状识别与分类
//python
python
import cv2
import numpy as np
from matplotlib import pyplot as plt
#读取图像
img = cv2.imread('shapes.jpg')
#转换为灰度图像
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#高斯模糊
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
#阈值分割
ret, thresh = cv2.threshold(blurred, 127, 255, cv2.THRESH_BINARY)
#检测轮廓
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#形状识别函数
def classify_shape(contour):
shape = "unknown"
#计算轮廓周长
peri = cv2.arcLength(contour, True)
#轮廓近似
approx = cv2.approxPolyDP(contour, 0.04 peri, True)
#计算轮廓面积
area = cv2.contourArea(contour)
#计算最小外接圆
(x, y), radius = cv2.minEnclosingCircle(contour)
circle_area = np.pi radius 2
#计算圆形度(轮廓面积与最小外接圆面积的比值)
circularity = area / circle_area
#根据顶点数量和圆形度判断形状
if len(approx) == 3:
shape = "三角形"
elif len(approx) == 4:
#计算宽高比
x, y, w, h = cv2.boundingRect(approx)
aspect_ratio = w / float(h)
if 0.95 <= aspect_ratio <= 1.05:
shape = "正方形"
else:
shape = "矩形"
elif len(approx) == 5:
shape = "五边形"
elif len(approx) == 6:
shape = "六边形"
elif circularity > 0.8:
shape = "圆形"
else:
shape = "椭圆"
return shape
#识别并标记形状
img_classified = img.copy()
for contour in contours:
#过滤小轮廓
if cv2.contourArea(contour) < 100:
continue
#识别形状
shape = classify_shape(contour)
#计算质心
M = cv2.moments(contour)
cx = int(M['m10'] / M['m00'])
cy = int(M['m01'] / M['m00'])
#绘制轮廓和形状名称
cv2.drawContours(img_classified, [contour], 1, (0, 255, 0), 2)
cv2.putText(img_classified, shape, (cx 30, cy), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
#显示结果
plt.figure(figsize=(10, 6))
plt.imshow(cv2.cvtColor(img_classified, cv2.COLOR_BGR2RGB))
plt.title('形状识别结果')
plt.axis('off')
plt.show()
- 物体尺寸测量
//python
python
import cv2
import numpy as np
from matplotlib import pyplot as plt
#读取图像
img = cv2.imread('coins.jpg')
#转换为灰度图像
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#高斯模糊
blurred = cv2.GaussianBlur(gray, (11, 11), 0)
#Canny边缘检测
canny = cv2.Canny(blurred, 30, 150)
#膨胀操作
dilated = cv2.dilate(canny, None, iterations=2)
#检测轮廓
contours, hierarchy = cv2.findContours(dilated.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#按面积排序,假设最大的轮廓是参考物体(例如硬币)
contours = sorted(contours, key=cv2.contourArea, reverse=True)
#假设最大的硬币直径为25mm(实际应用中需要校准)
reference_diameter = 25.0 mm
#计算参考物体的像素直径
if contours:
reference_contour = contours[0]
(x, y), radius = cv2.minEnclosingCircle(reference_contour)
reference_radius_px = radius
reference_diameter_px = 2 radius
#计算像素到毫米的比例
px_to_mm = reference_diameter / reference_diameter_px
#测量所有硬币
img_measurement = img.copy()
for i, contour in enumerate(contours):
#计算最小外接圆
(x, y), radius = cv2.minEnclosingCircle(contour)
center = (int(x), int(y))
radius_px = radius
radius_mm = radius_px px_to_mm
diameter_mm = 2 radius_mm
#绘制结果
cv2.circle(img_measurement, center, int(radius_px), (0, 255, 0), 2)
cv2.putText(img_measurement, f"D: {diameter_mm:.1f}mm",
(int(x radius_px), int(y radius_px) 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
#显示结果
plt.figure(figsize=(12, 6))
plt.subplot(121), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)), plt.title('原图')
plt.subplot(122), plt.imshow(cv2.cvtColor(img_measurement, cv2.COLOR_BGR2RGB)), plt.title('尺寸测量结果')
plt.tight_layout()
plt.show()
六、总结与技巧
- 轮廓特征分析的应用场景
- 目标识别:通过轮廓特征识别不同形状的物体
- 尺寸测量:测量物体的长度、宽度、面积等参数
- 质量控制:检测产品的形状是否符合要求
- 机器人视觉:引导机器人进行抓取、装配等操作
- 技巧与注意事项
- 图像预处理:在进行轮廓检测前,使用高斯模糊减少噪声,提高轮廓检测的准确性
- 轮廓过滤:通过面积、周长等特征过滤掉噪声或不需要的小轮廓
- 特征选择:根据实际应用选择合适的轮廓特征,如形状识别使用顶点数量和圆形度,尺寸测量使用边界矩形或最小外接圆
- 精度校准:在实际测量应用中,需要使用已知尺寸的参考物体进行校准
- 旋转不变性:使用Hu矩等具有旋转不变性的特征,可以在目标旋转时仍然准确识别
- 常见问题与解决方案
- 轮廓不完整:调整阈值参数或使用边缘检测代替直接阈值分割
- 虚假轮廓:使用形态学操作(如开运算、闭运算)去除噪声
- 轮廓重叠:使用更高级的图像分割算法,如分水岭算法或GrabCut算法
通过掌握轮廓特征分析技术,可以实现对图像中物体的形状、大小、位置等信息的精确提取和分析,为计算机视觉应用提供重要的基础支持。