mi-task/utils/decode_arrow.py
2025-08-18 11:06:42 +08:00

424 lines
14 KiB
Python
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import cv2
import numpy as np
def detect_arrow_direction(image, observe=False, delay=500):
"""
从图像中提取绿色箭头并判断其指向方向(左或右)
参数:
image: 输入图像,可以是文件路径或者已加载的图像数组
observe: 是否输出中间状态信息和可视化结果默认为False
delay: 展示每个步骤的等待时间(毫秒)默认为500ms
返回:
direction: 字符串,"left"表示左箭头,"right"表示右箭头,"unknown"表示无法确定
"""
# 如果输入是字符串(文件路径),则加载图像
if isinstance(image, str):
img = cv2.imread(image)
else:
img = image.copy()
if img is None:
print("无法加载图像")
return "unknown"
if observe:
print("步骤1: 原始图像已加载")
cv2.imshow("原始图像", img)
cv2.waitKey(delay)
# 转换到HSV颜色空间以便更容易提取绿色
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
if observe:
print("步骤2: 转换到HSV颜色空间")
cv2.imshow("HSV图像", hsv)
cv2.waitKey(delay)
# 绿色的HSV范围
# 调整这些值以匹配图像中绿色的具体色调··
lower_green = np.array([40, 50, 50])
upper_green = np.array([80, 255, 255])
# 创建绿色的掩码
mask = cv2.inRange(hsv, lower_green, upper_green)
if observe:
print("步骤3: 创建绿色掩码")
cv2.imshow("绿色掩码", mask)
cv2.waitKey(delay)
# 应用掩码,只保留绿色部分
green_only = cv2.bitwise_and(img, img, mask=mask)
if observe:
print("步骤4: 提取绿色部分")
cv2.imshow("只保留绿色", green_only)
cv2.waitKey(delay)
# 将掩码转为灰度图
gray = mask.copy()
# 查找轮廓
contours, _ = cv2.findContours(gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 如果没有找到轮廓,返回未知
if not contours:
if observe:
print("未找到轮廓")
return "unknown"
if observe:
print(f"步骤5: 找到 {len(contours)} 个轮廓")
contour_img = img.copy()
cv2.drawContours(contour_img, contours, -1, (0, 255, 0), 2)
cv2.imshow("所有轮廓", contour_img)
cv2.waitKey(delay)
# 找到最大的轮廓(假设是箭头)
max_contour = max(contours, key=cv2.contourArea)
if observe:
print("步骤6: 提取最大轮廓")
max_contour_img = img.copy()
cv2.drawContours(max_contour_img, [max_contour], -1, (0, 0, 255), 2)
cv2.imshow("最大轮廓", max_contour_img)
cv2.waitKey(delay)
# 使用多边形近似轮廓
epsilon = 0.02 * cv2.arcLength(max_contour, True)
approx = cv2.approxPolyDP(max_contour, epsilon, True)
if observe:
print(f"步骤7: 多边形近似,顶点数: {len(approx)}")
approx_img = img.copy()
cv2.drawContours(approx_img, [approx], -1, (255, 0, 0), 2)
cv2.imshow("多边形近似", approx_img)
cv2.waitKey(delay)
# 计算凸包
hull = cv2.convexHull(max_contour)
if observe:
print("步骤8: 计算凸包")
hull_img = img.copy()
cv2.drawContours(hull_img, [hull], -1, (0, 255, 0), 2)
cv2.imshow("凸包", hull_img)
cv2.waitKey(delay)
# 计算凸缺陷
if len(max_contour) > 3:
defects = cv2.convexityDefects(max_contour, cv2.convexHull(max_contour, returnPoints=False))
else:
if observe:
print("轮廓点太少,无法准确判断")
return "unknown" # 轮廓点太少,无法准确判断
if defects is None:
if observe:
print("未找到凸缺陷")
return "unknown"
if observe:
print(f"步骤9: 计算凸缺陷,找到 {defects.shape[0]} 个缺陷点")
defect_img = img.copy()
for i in range(defects.shape[0]):
s, e, f, d = defects[i, 0]
start = tuple(max_contour[s][0])
end = tuple(max_contour[e][0])
far = tuple(max_contour[f][0])
cv2.circle(defect_img, far, 5, (0, 0, 255), -1)
cv2.imshow("凸缺陷", defect_img)
cv2.waitKey(delay)
# 获取轮廓的最小外接矩形
rect = cv2.minAreaRect(max_contour)
box = cv2.boxPoints(rect)
box = np.int0(box)
if observe:
print("步骤10: 获取最小外接矩形")
rect_img = img.copy()
cv2.drawContours(rect_img, [box], 0, (255, 0, 0), 2)
cv2.imshow("最小外接矩形", rect_img)
cv2.waitKey(delay)
# 获取中心点和角度
center = rect[0]
angle = rect[2]
if observe:
print(f"矩形中心: {center}, 角度: {angle}")
# 计算轮廓的矩,用于确定箭头方向
M = cv2.moments(max_contour)
# 避免除以零
if M["m00"] != 0:
cx = int(M["m10"] / M["m00"])
cy = int(M["m01"] / M["m00"])
else:
cx, cy = int(center[0]), int(center[1])
if observe:
print(f"步骤11: 计算质心 - 坐标: ({cx}, {cy})")
center_img = img.copy()
cv2.circle(center_img, (cx, cy), 5, (255, 0, 0), -1)
cv2.imshow("质心", center_img)
cv2.waitKey(delay)
# 改进的箭头尖端检测算法
# 1. 找到所有凸缺陷点
defect_points = []
for i in range(defects.shape[0]):
s, e, f, d = defects[i, 0]
start = tuple(max_contour[s][0])
end = tuple(max_contour[e][0])
far = tuple(max_contour[f][0])
# 计算缺陷点到中心的距离
dist = np.sqrt((far[0] - cx) ** 2 + (far[1] - cy) ** 2)
if observe:
print(f"缺陷点 {i}: 位置 {far}, 到中心距离: {dist:.2f}")
# 记录凸缺陷点及其距离
defect_points.append({
'point': far,
'distance': dist,
'start': start,
'end': end
})
# 没有缺陷点,使用矩形判断
if not defect_points:
# 判断逻辑将在后面处理
arrow_tip = None
else:
# 2. 按距离对缺陷点排序
defect_points.sort(key=lambda x: x['distance'], reverse=True)
# 3. 获取最远的几个缺陷点(可能的尖端候选)
top_n = min(3, len(defect_points))
candidates = defect_points[:top_n]
# 4. 分析这些点的位置分布
left_candidates = [p for p in candidates if p['point'][0] < cx]
right_candidates = [p for p in candidates if p['point'][0] >= cx]
# 5. 根据候选点的分布判断箭头朝向
if len(left_candidates) > len(right_candidates):
# 左侧候选点更多,箭头可能指向左边
arrow_tip = max(left_candidates, key=lambda x: x['distance'])['point']
arrow_direction = "left"
elif len(right_candidates) > len(left_candidates):
# 右侧候选点更多,箭头可能指向右边
arrow_tip = max(right_candidates, key=lambda x: x['distance'])['point']
arrow_direction = "right"
else:
# 候选点分布均衡,使用最远的点
arrow_tip = candidates[0]['point']
# 根据最远点的位置判断
if arrow_tip[0] < cx:
arrow_direction = "left"
else:
arrow_direction = "right"
if observe and arrow_tip:
print(f"步骤12: 找到可能的箭头尖端 - 位置: {arrow_tip}")
tip_img = img.copy()
cv2.circle(tip_img, arrow_tip, 8, (0, 255, 255), -1)
cv2.line(tip_img, (cx, cy), arrow_tip, (255, 0, 255), 2)
cv2.imshow("箭头尖端", tip_img)
cv2.waitKey(delay)
# 使用多种特征综合判断箭头方向
if arrow_tip is None:
# 如果没有找到明显的箭头尖端,使用最小外接矩形来判断
width = rect[1][0]
height = rect[1][1]
if observe:
print(f"未找到明显尖端,使用矩形判断 - 宽: {width}, 高: {height}")
# 矩形的方向向量
if width > height: # 水平箭头
# 考虑角度的定义
if angle > 45:
arrow_direction = "left"
else:
arrow_direction = "right"
else: # 垂直箭头,不在我们的考虑范围内
if observe:
print("垂直箭头,不在考虑范围")
return "unknown"
else:
# 已经在上面的代码中确定了方向
pass
# 附加检查:计算轮廓边界上的点,确认箭头的形状特征
x, y, w, h = cv2.boundingRect(max_contour)
aspect_ratio = float(w) / h
# 计算轮廓周长和面积
perimeter = cv2.arcLength(max_contour, True)
area = cv2.contourArea(max_contour)
# 计算轮廓的形状复杂度
complexity = perimeter / (4 * np.sqrt(area))
if observe:
print(f"轮廓分析 - 宽高比: {aspect_ratio:.2f}, 复杂度: {complexity:.2f}")
# 箭头形状特征检查
# 一般来说,箭头的复杂度会在一定范围内
if 1.1 < complexity < 3.0:
# 根据形状特征进一步确认方向判断
# 使用质心位置相对于轮廓边界的偏移
relative_cx = (cx - x) / w
if observe:
print(f"质心相对位置: {relative_cx:.2f}")
# 如果质心偏向左侧,箭头可能指向右侧
# 如果质心偏向右侧,箭头可能指向左侧
# 这是一个启发式规则,可能需要根据具体箭头形状调整
if relative_cx < 0.4:
# 质心在左侧,可能是右箭头
if arrow_direction == "left":
# 当前方法判断为左,但质心位置特征表明可能是右
# 增加额外的检查
if aspect_ratio > 1.5: # 宽大于高
arrow_direction = "right"
elif relative_cx > 0.6:
# 质心在右侧,可能是左箭头
if arrow_direction == "right":
# 当前方法判断为右,但质心位置特征表明可能是左
if aspect_ratio > 1.5: # 宽大于高
arrow_direction = "left"
if observe:
print(f"基于综合特征判断: {arrow_direction}箭头")
return arrow_direction
def visualize_arrow_detection(image, save_path=None, observe=False, delay=500):
"""
可视化箭头检测过程,显示中间结果
参数:
image: 输入图像,可以是文件路径或者已加载的图像数组
save_path: 保存结果图像的路径(可选)
observe: 是否输出中间状态信息和可视化结果默认为False
delay: 展示每个步骤的等待时间(毫秒)默认为500ms
"""
# 如果输入是字符串(文件路径),则加载图像
if isinstance(image, str):
img = cv2.imread(image)
else:
img = image.copy()
if img is None:
print("无法加载图像")
return
if observe:
print("\n开始可视化箭头检测过程")
# 转换到HSV颜色空间
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# 绿色的HSV范围
lower_green = np.array([40, 50, 50])
upper_green = np.array([80, 255, 255])
# 创建绿色的掩码
mask = cv2.inRange(hsv, lower_green, upper_green)
# 应用掩码,只保留绿色部分
green_only = cv2.bitwise_and(img, img, mask=mask)
# 查找轮廓
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 创建输出图像
output = img.copy()
# 如果找到轮廓,绘制最大轮廓
if contours:
max_contour = max(contours, key=cv2.contourArea)
cv2.drawContours(output, [max_contour], -1, (0, 0, 255), 2)
# 获取轮廓的最小外接矩形
rect = cv2.minAreaRect(max_contour)
box = cv2.boxPoints(rect)
box = np.int0(box)
cv2.drawContours(output, [box], 0, (255, 0, 0), 2)
# 计算轮廓的矩
M = cv2.moments(max_contour)
# 避免除以零
if M["m00"] != 0:
cx = int(M["m10"] / M["m00"])
cy = int(M["m01"] / M["m00"])
# 绘制中心点
cv2.circle(output, (cx, cy), 5, (255, 0, 0), -1)
# 绘制凸包
hull = cv2.convexHull(max_contour)
cv2.drawContours(output, [hull], 0, (0, 255, 0), 2)
# 绘制凸缺陷
if len(max_contour) > 3:
defects = cv2.convexityDefects(max_contour, cv2.convexHull(max_contour, returnPoints=False))
if defects is not None:
for i in range(defects.shape[0]):
s, e, f, d = defects[i, 0]
start = tuple(max_contour[s][0])
end = tuple(max_contour[e][0])
far = tuple(max_contour[f][0])
cv2.circle(output, far, 5, (0, 0, 255), -1) # 在凸缺陷点绘制红色圆点
# 获取箭头方向
direction = detect_arrow_direction(img, observe=observe, delay=delay)
# 在图像上添加方向文本
cv2.putText(output, f"Direction: {direction}", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
# 如果提供了保存路径,保存结果图像
if save_path:
cv2.imwrite(save_path, output)
if observe:
print(f"结果已保存到: {save_path}")
# 创建一个包含所有图像的窗口
result = np.hstack((img, green_only, output))
# 调整大小以便查看
scale_percent = 50 # 缩放到原来的50%
width = int(result.shape[1] * scale_percent / 100)
height = int(result.shape[0] * scale_percent / 100)
dim = (width, height)
resized = cv2.resize(result, dim, interpolation=cv2.INTER_AREA)
# 显示结果
cv2.imshow('Arrow Detection Process', resized)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 用法示例
if __name__ == "__main__":
# 替换为实际图像路径
image_path = "path/to/arrow/image.png"
# 检测箭头方向使用较长的延迟时间1500毫秒
direction = detect_arrow_direction(image_path, observe=True, delay=1500)
print(f"检测到的箭头方向: {direction}")
# 可视化检测过程,使用较长的延迟时间
visualize_arrow_detection(image_path, observe=True, delay=1500)