OpenCV图像测量:两种高效方法对比
在计算机视觉领域,图像测量是一项常见而重要的任务。本文将详细介绍两种基于OpenCV的图像测量方法,并对它们的实现原理、优缺点及适用场景进行深入分析。
方法一:基于参考物体宽度的测量方法
核心原理:第一种方法通过设定已知宽度的参考物体(通常放置在图像最左侧),计算像素与实际尺寸的比例关系,从而测量图像中其他物体的尺寸。
代码实现分析:
# 导入必要的库
from scipy.spatial import distance as dist
from imutils import perspective
from imutils import contours
import numpy as np
import argparse
import imutils
import cv2
# 定义中点函数
def midpoint(ptA, ptB):
return ((ptA[0] + ptB[0]) * 0.5, (ptA[1] + ptB[1]) * 0.5)
# 设置内部参数
args = {
"image": r"D:\data\05.bmp",
"width": 25.0 # 参考物体的实际宽度
}
以上代码段首先导入了必要的库,并定义了中点计算函数和参数设置。
# 图像预处理
img = cv2.imread(args["image"])
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray, 120, 255, cv2.THRESH_BINARY_INV)
# 膨胀操作增强轮廓
kernel = np.ones((3, 3), np.uint8)
thresh = cv2.dilate(thresh, kernel, iterations=1)
# 填充图像四角以消除边缘干扰
height, width = thresh.shape
thresh[0:120, 0:160] = 0 # 左上角
thresh[0:150, width - 180:] = 0 # 右上角
thresh[height - 120:, 0:160] = 0 # 左下角
thresh[height - 150:, width - 160:] = 0 # 右下角
预处理阶段包括读取图像、灰度转换、二值化和膨胀操作,最后填充图像四角以消除边缘干扰。
# 寻找物体轮廓并排序
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
(cnts, _) = contours.sort_contours(cnts)
# 初始化像素与实际尺寸的比例
pixelsPerMetric = None
# 遍历每个轮廓进行测量
for c in cnts:
if cv2.contourArea(c) < 10000: # 忽略面积过小的轮廓
continue
# 计算最小外接矩形
box = cv2.minAreaRect(c)
box = cv2.boxPoints(box)
box = np.array(box, dtype="int")
box = perspective.order_points(box)
# 计算各边中点并绘制
(tl, tr, br, bl) = box
(tltrX, tltrY) = midpoint(tl, tr)
(blbrX, blbrY) = midpoint(bl, br)
(tlblX, tlblY) = midpoint(tl, bl)
(trbrX, trbrY) = midpoint(tr, br)
# 计算像素距离
dA = dist.euclidean((tltrX, tltrY), (blbrX, blbrY))
dB = dist.euclidean((tlblX, tlblY), (trbrX, trbrY))
# 设置比例(使用最左侧物体的已知宽度)
if pixelsPerMetric is None:
pixelsPerMetric = dB / args["width"]
# 计算实际尺寸
dimA = dA / pixelsPerMetric * 0.98 # 0.98为修正系数
dimB = dB / pixelsPerMetric * 0.98
# 绘制测量结果
cv2.putText(orig, "{:.3f}mm".format(dimA), (int(tltrX - 15), int(tltrY - 10)),
cv2.FONT_HERSHEY_SIMPLEX, 0.65, (255, 255, 255), 1)
cv2.putText(orig, "{:.3f}mm".format(dimB), (int(trbrX + 10), int(trbrY)),
cv2.FONT_HERSHEY_SIMPLEX, 0.65, (255, 255, 255), 1)
测量阶段首先寻找并排序轮廓,然后为每个轮廓计算最小外接矩形,通过参考物体的宽度建立像素与实际尺寸的转换关系,最后计算并显示实际尺寸。
方法二:基于硬币作为参考的测量方法
核心原理:第二种方法使用硬币作为参考物体(已知实际直径),通过检测图像中最大的圆形轮廓来识别硬币,然后计算像素与实际尺寸的比例关系。
代码实现分析:
import cv2
import numpy as np
import matplotlib.pyplot as plt
def find_and_measure_objects(image_path, coin_real_diameter_mm=25.0):
# 读取图像
img = cv2.imread(image_path)
if img is None:
raise ValueError("图像加载失败,请检查路径")
# 转换为灰度图并二值化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray, 120, 255, cv2.THRESH_BINARY_INV)
# 填充四角消除干扰
height, width = thresh.shape
thresh[0:120, 0:160] = 0 # 左上角
thresh[0:140, width - 180:] = 0 # 右上角
thresh[height - 120:, 0:160] = 0 # 左下角
thresh[height - 150:, width - 160:] = 0 # 右下角
该方法封装在一个函数中,首先进行图像读取、灰度转换、二值化和边缘处理。
# 查找轮廓并找到最大的轮廓(假设是硬币)
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
max_contour = max(contours, key=cv2.contourArea)
# 获取硬币的最小外接圆
(x, y), radius = cv2.minEnclosingCircle(max_contour)
center = (int(x), int(y))
radius = int(radius)
diameter_px = 2 * radius
# 计算像素到毫米的转换比例
px_to_mm = coin_real_diameter_mm / coin_diameter_px
这部分代码查找轮廓并找到最大的轮廓(假设为硬币),然后计算硬币的最小外接圆,基于已知的硬币实际直径建立像素与毫米的转换关系。
# 遍历所有轮廓并测量
measurements = []
for i, contour in enumerate(contours):
area = cv2.contourArea(contour)
if area < 20000 or contour is max_contour: # 忽略小物体和硬币本身
continue
# 获取最小外接矩形
rect = cv2.minAreaRect(contour)
box = cv2.boxPoints(rect)
box = np.int0(box)
# 计算尺寸
edge1 = np.linalg.norm(box[0] - box[1])
edge2 = np.linalg.norm(box[1] - box[2])
width_px, height_px = max(edge1, edge2), min(edge1, edge2)
# 转换为真实尺寸
width_mm = width_px * px_to_mm
height_mm = height_px * px_to_mm
# 存储测量结果
measurements.append({
'id': i + 1,
'width_px': width_px,
'height_px': height_px,
'width_mm': width_mm,
'height_mm': height_mm
})
测量阶段遍历所有轮廓,计算每个物体的最小外接矩形,并根据像素与毫米的转换关系计算实际尺寸。
# 使用matplotlib显示结果
plt.figure(figsize=(15, 5))
plt.subplot(131), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.title('Original Image'), plt.axis('off')
plt.subplot(132), plt.imshow(thresh, cmap='gray')
plt.title('Threshold Image'), plt.axis('off')
plt.subplot(133), plt.imshow(cv2.cvtColor(result_img, cv2.COLOR_BGR2RGB))
plt.title('Measurement Results'), plt.axis('off')
plt.tight_layout()
plt.show()
# 打印测量结果表格
print("\n物体测量结果:")
print(f"{'ID':<5}{'宽度(mm)':<10}{'高度(mm)':<10}{'宽度(px)':<10}{'高度(px)':<10}")
print("-" * 45)
for m in measurements:
print(f"{m['id']:<5}{m['width_mm']:<10.3f}{m['height_mm']:<10.13}{m['width_px']:<10.3f}{m['height_px']:<10.3f}")
return result_img, measurements
最后,该方法使用matplotlib显示原始图像、二值化图像和测量结果,并以表格形式打印测量数据。
两种方法的对比分析
1、相同点
|
1. 都使用OpenCV进行图像处理和轮廓检测 |
| 2. 都需要参考物体来建立像素与实际尺寸的转换关系 |
| 3. 都采用二值化方法突出物体轮廓 |
| 4. 都使用最小外接矩形来表示物体的尺寸 |
| 5. 都填充图像四角以消除边缘干扰 |
2、不同点
| 对比项 | 方法一 | 方法二 |
|---|---|---|
| 参考物体 | 已知宽度的物体(通常放在最左侧) | 硬币(已知直径) |
| 代码结构 | 线性执行结构 | 函数封装结构 |
| 轮廓排序 | 从左到右排序 | 无需排序,通过面积识别参考物体 |
| 显示方式 | 使用 OpenCV 的 imshow | 使用 matplotlib 显示多图对比 |
| 结果输出 | 仅在图像上显示 | 图像显示 + 表格数据输出 |
| 适用场景 | 有规则排列的物体测量 | 包含圆形参考物的任意场景 |
优缺点分析
方法一优点:
- 实现简单直观
- 对于规则排列的物体测量效率高
- 无需复杂的轮廓识别算法
方法一缺点:
- 依赖参考物体的位置(必须在最左侧)
- 代码结构较为松散
- 仅支持在图像上显示结果
方法二优点:
- 封装性更好,易于复用
- 参考物体(硬币)放置位置更灵活
- 结果可视化更丰富(多图对比 + 表格数据)
- 代码结构更清晰
方法二缺点:
- 依赖于硬币在图像中是最大的圆形物体
- 当图像中有多个类似大小的圆形物体时可能误判
- 实现相对复杂
适用场景推荐
方法一适用场景:
- 生产线检测中物体规则排列的场景
- 需要快速实现简单测量的场景
- 对代码封装要求不高的场景
方法二适用场景:
- 通用物体测量场景
- 需要美观结果展示的场景
- 多次复用测量功能的场景
- 有圆形参考物体(如硬币)的场景
代码优化建议
方法一优化建议
1. 封装为函数:
def measure_objects_by_reference(image_path, ref_width_mm, min_area=10000):
# 将原代码封装为函数
# 返回测量结果和处理后的图像
2. 增加异常处理:
try:
img = cv2.imread(image_path)
if img is None:
raise FileNotFoundError(f"无法读取图像:{image_path}")
except Exception as e:
print(f"图像读取错误:{e}")
return None, None
3. 增加参数可调性:
def measure_objects_by_reference(image_path, ref_width_mm,
threshold_value=120,
min_area=10000,
correction_factor=0.98):
# 实现代码
方法二优化建议
1. 增加硬币检测的鲁棒性:
def find_coin_contour(contours):
# 不仅仅依靠面积,还可以结合圆形度等特征
coin_contour = None
max_circularity = -1
for contour in contours:
if cv2.contourArea(contour) < 10000: # 忽略小轮廓
continue
# 计算圆形度 (4π*面积/周长²)
perimeter = cv2.arcLength(contour, True)
if perimeter == 0:
continue
circularity = 4 * np.pi * cv2.contourArea(contour) / (perimeter ** 2)
# 选择圆形度最高且面积较大的轮廓作为硬币
if circularity > max_circularity and circularity > 0.8: # 圆形度阈值
max_circularity = circularity
coin_contour = contour
return coin_contour
2. 增加图像预处理的灵活性:
def preprocess_image(img, threshold_value=120,
fill_corners=True,
corner_fill_sizes=None):
# 实现灵活的图像预处理
3. 支持多种显示模式:
def display_results(img, thresh, result_img, display_mode='matplotlib'):
if display_mode == 'opencv':
cv2.imshow("Original", img)
cv2.imshow("Threshold", thresh)
cv2.imshow("Results", result_img)
cv2.waitKey(0)
else:
# matplotlib显示代码
总结
本文详细介绍了两种基于OpenCV的图像测量方法,它们各有优缺点和适用场景。
方法一实现简单,适合规则排列物体的测量;
方法二封装性更好,结果展示更丰富,适合更通用的测量场景。
在实际应用中,可以根据具体需求选择合适的方法,或者结合两种方法的优点进行改进。例如,可以使用方法二的封装结构,同时允许用户选择不同类型的参考物体,从而提高测量的灵活性和准确性。
图像测量技术在工业检测、物体识别、机器人视觉等领域有着广泛的应用前景,通过不断优化算法和提高测量精度,可以进一步拓展其应用范围。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)