0.我要做什么?
用OpenCV启动摄像头后,在预览界面用鼠标点击两点,画一条线。(没错,这是上一个小节的需求描述)
然后,显示出该线段的像素距离。
1.初步方案或待解决的问题
有了上一篇文章的基础,留给本文的就两个问题:
一个是如何显示长度信息,一个如何计算长度。
前者使用cv2.putText()方法,而后者通过欧氏距离来计算。
其他交互方面:
- 实时显示鼠标位置与预览线。所谓的预览线是鼠标点击一次后,就有了起点,那么鼠标抬起后,有一根绿色线跟着鼠标走。
- 支持多条线段的测量。
2.函数cv2.putText()介绍
该函数主要作用是在图像上绘制文本,原型如下:
cv2.putText(img, text, org, fontFace, fontScale, color, thickness, lineType, bottomLeftOrigin)
参数属实有点多,下面来看看说明:
参数 | 类型 | 说明 | 示例值 |
---|---|---|---|
img | Mat /ndarray | 目标图像(直接修改原图) | frame |
text | str | 要绘制的文本内容 | "Cat: 95%" |
org | (x, y) | 文本左下角的坐标(原点默认左上角) | (50, 100) |
fontFace | int | 字体类型(如 FONT_HERSHEY_SIMPLEX ) | cv2.FONT_HERSHEY_SIMPLEX |
fontScale | float | 字体缩放因子(1.0 为默认大小) | 0.8 |
color | (B, G, R) | 文本颜色(BGR 格式) | (0, 0, 255) (红色) |
thickness | int | 笔画粗细(像素) | 2 |
lineType | int | 线条类型(推荐抗锯齿 LINE_AA ) | cv2.LINE_AA |
bottomLeftOrigin | bool | 若为 True ,坐标原点移至左下角 | False (默认) |
举个代码例子:
import cv2
image = cv2.imread(r'../defect_inspection/images/glove.png')
cv2.putText(image, "Text Test", (20, 80),
cv2.FONT_HERSHEY_PLAIN, 1.5, (0, 255, 255), 2)
cv2.imshow('Text Test', image)
cv2.waitKey(0)
3.欧氏距离
欧氏距离(Euclidean Distance)是欧几里得空间中两点间直线距离的度量方式,也是最常用的距离计算方法之一。其核心是通过勾股定理计算多维空间中点的绝对距离。
本次我们只看二维的,多维暂时用不到,就不烧脑了。
计算公式:
- 二维空间(如平面坐标系)
点 (A(x_1, y_1)) 与 (B(x_2, y_2)) 的欧氏距离:
d=(x2−x1)2+(y2−y1)2d = \sqrt{(x_2 - x_1)^2 + (y_2 - y_1)^2}d=(x2−x1)2+(y2−y1)2
示例:点 (A(1,2)) 和 (B(4,6)) 的距离为 (4−1)2+(6−2)2=9+16=5\sqrt{(4-1)^2 + (6-2)^2} = \sqrt{9+16} = 5(4−1)2+(6−2)2=9+16=5。
- 三维空间
点 (A(x_1,y_1,z_1)) 与 (B(x_2,y_2,z_2)) 的距离:
d=(x2−x1)2+(y2−y1)2+(z2−z1)2 d = \sqrt{(x_2 - x_1)^2 + (y_2 - y_1)^2 + (z_2 - z_1)^2} d=(x2−x1)2+(y2−y1)2+(z2−z1)2
示例:点 (A(1,2,3)) 和 (B(4,5,6)) 的距离为 9+9+9≈5.196\sqrt{9+9+9} \approx 5.1969+9+9≈5.196。
代码例子:
pt1 = points[-2]
pt2 = points[-1]
distance = np.sqrt((pt2[0] - pt1[0]) ** 2 + (pt2[1] - pt1[1]) ** 2)
4.代码实现
直线颜色 (B, G, R) 格式
绿色:(0, 255, 0)
蓝色:(255, 0, 0)
import cv2
import numpy as np
points = [] # 存储坐标点
current_pos = (0, 0) # 当前鼠标位置
line_distances = [] # 存储每条线的长度
def mouse_draw(event, x, y, flags, param):
global points, current_pos
current_pos = (x, y) # 实时更新鼠标位置
if event == cv2.EVENT_LBUTTONDOWN:
points.append((x, y)) # 添加当前点到列表
# 每当有偶数个点时计算线段长度
if len(points) % 2 == 0:
pt1 = points[-2]
pt2 = points[-1]
distance = np.sqrt((pt2[0] - pt1[0]) ** 2 + (pt2[1] - pt1[1]) ** 2)
line_distances.append(round(distance, 2))
cv2.namedWindow('image')
cv2.setMouseCallback('image', mouse_draw)
cap = cv2.VideoCapture(1) # 0是默认摄像头,根据自己的摄像头设置
while True:
ret, frame = cap.read()
if not ret:
break
# 绘制所有已完成的线段和长度
for i in range(0, len(points) // 2):
idx = 2 * i
pt1 = points[idx]
pt2 = points[idx + 1]
cv2.line(frame, pt1, pt2, (255, 0, 0), 2)
# 在线段中点显示长度
mid_x = (pt1[0] + pt2[0]) // 2
mid_y = (pt1[1] + pt2[1]) // 2
cv2.putText(frame, f"{line_distances[i]}px",
(mid_x, mid_y), cv2.FONT_HERSHEY_SIMPLEX,
0.7, (0, 255, 255), 2)
# 实时绘制未完成的线段
if len(points) % 2 == 1:
cv2.line(frame, points[-1], current_pos, (0, 255, 0), 2)
# 显示当前状态
status = f"Points: {len(points)} | Next: {'Start' if len(points) % 2 == 0 else 'End'}"
cv2.putText(frame, status, (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
cv2.imshow('image', frame)
key = cv2.waitKey(30)
if key == ord('q'):
break
elif key == ord('c'): # 清除功能
points = []
line_distances = []
cap.release()
cv2.destroyAllWindows()
运行效果如下:
至此,我们向真正的测量又迈进了一步!加油!