diff --git a/res/arrows/test/arrow_detection_report.txt b/res/arrows/test/arrow_detection_report.txt new file mode 100644 index 0000000..f0f41c4 --- /dev/null +++ b/res/arrows/test/arrow_detection_report.txt @@ -0,0 +1,13 @@ +箭头方向检测 - 测试报告 +======================= + +测试日期: 2025-05-13 23:48:36 +测试图像总数: 8 + +总体准确率: 100.00% +各方向准确率: + - left: 100.00% + - right: 100.00% + +平均处理时间: 5.30 毫秒 + diff --git a/res/arrows/test/arrow_detection_result.jpg b/res/arrows/test/arrow_detection_result.jpg index c1cfc51..ef6cf1c 100644 Binary files a/res/arrows/test/arrow_detection_result.jpg and b/res/arrows/test/arrow_detection_result.jpg differ diff --git a/res/arrows/test/arrow_detection_results.csv b/res/arrows/test/arrow_detection_results.csv new file mode 100644 index 0000000..09d7203 --- /dev/null +++ b/res/arrows/test/arrow_detection_results.csv @@ -0,0 +1,9 @@ +图像文件,真实方向,检测方向,是否正确,处理时间(秒),结果文件 +image-1.png,left,left,True,0.00851130485534668,left_image-1_result.jpg +image-3.png,left,left,True,0.007372140884399414,left_image-3_result.jpg +image-2.png,left,left,True,0.006041288375854492,left_image-2_result.jpg +image-5.png,left,left,True,0.002747058868408203,left_image-5_result.jpg +image-4.png,left,left,True,0.006627082824707031,left_image-4_result.jpg +image-1.png,right,right,True,0.003347158432006836,right_image-1_result.jpg +image-3.png,right,right,True,0.004812955856323242,right_image-3_result.jpg +image-2.png,right,right,True,0.002933979034423828,right_image-2_result.jpg diff --git a/res/arrows/test/arrow_detection_stats.png b/res/arrows/test/arrow_detection_stats.png new file mode 100644 index 0000000..8c3f8c1 Binary files /dev/null and b/res/arrows/test/arrow_detection_stats.png differ diff --git a/res/arrows/test/left_image-1_result.jpg b/res/arrows/test/left_image-1_result.jpg new file mode 100644 index 0000000..2142087 Binary files /dev/null and b/res/arrows/test/left_image-1_result.jpg differ diff --git a/res/arrows/test/left_image-2_result.jpg b/res/arrows/test/left_image-2_result.jpg new file mode 100644 index 0000000..96b6869 Binary files /dev/null and b/res/arrows/test/left_image-2_result.jpg differ diff --git a/res/arrows/test/left_image-3_result.jpg b/res/arrows/test/left_image-3_result.jpg new file mode 100644 index 0000000..751fc7d Binary files /dev/null and b/res/arrows/test/left_image-3_result.jpg differ diff --git a/res/arrows/test/left_image-4_result.jpg b/res/arrows/test/left_image-4_result.jpg new file mode 100644 index 0000000..c302a6e Binary files /dev/null and b/res/arrows/test/left_image-4_result.jpg differ diff --git a/res/arrows/test/left_image-5_result.jpg b/res/arrows/test/left_image-5_result.jpg new file mode 100644 index 0000000..20bd3f5 Binary files /dev/null and b/res/arrows/test/left_image-5_result.jpg differ diff --git a/res/arrows/test/right_image-1_result.jpg b/res/arrows/test/right_image-1_result.jpg new file mode 100644 index 0000000..02db569 Binary files /dev/null and b/res/arrows/test/right_image-1_result.jpg differ diff --git a/res/arrows/test/right_image-2_result.jpg b/res/arrows/test/right_image-2_result.jpg new file mode 100644 index 0000000..c8701e9 Binary files /dev/null and b/res/arrows/test/right_image-2_result.jpg differ diff --git a/res/arrows/test/right_image-3_result.jpg b/res/arrows/test/right_image-3_result.jpg new file mode 100644 index 0000000..ee748fb Binary files /dev/null and b/res/arrows/test/right_image-3_result.jpg differ diff --git a/test/task-arrow/batch_test_arrow.py b/test/task-arrow/batch_test_arrow.py new file mode 100644 index 0000000..dc33fa0 --- /dev/null +++ b/test/task-arrow/batch_test_arrow.py @@ -0,0 +1,197 @@ +#!/usr/bin/env python3 + +import os +import sys +import cv2 +import argparse +import time +from tqdm import tqdm +import pandas as pd +import matplotlib.pyplot as plt +import matplotlib as mpl + +# 设置中文字体支持 +plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei', 'Microsoft YaHei', 'WenQuanYi Micro Hei', 'sans-serif'] +plt.rcParams['axes.unicode_minus'] = False + +# 添加项目根目录到路径 +current_dir = os.path.dirname(os.path.abspath(__file__)) +project_root = os.path.dirname(os.path.dirname(current_dir)) +sys.path.append(project_root) + +from utils.decode_arrow import detect_arrow_direction, visualize_arrow_detection + +def batch_test_arrows(data_dir="res/arrows", save_dir="res/arrows/test", show_results=False): + """ + 批量测试箭头方向检测算法 + + 参数: + data_dir: 包含箭头图像的目录 + save_dir: 保存结果的目录 + show_results: 是否显示结果 + + 返回: + results_df: 包含测试结果的DataFrame + """ + # 确保保存目录存在 + os.makedirs(save_dir, exist_ok=True) + + # 保存结果的列表 + results = [] + + # 处理左右箭头子目录 + for direction in ["left", "right"]: + dir_path = os.path.join(data_dir, direction) + if not os.path.exists(dir_path): + print(f"警告: 目录 '{dir_path}' 不存在") + continue + + # 获取该方向的所有图像文件 + image_files = [f for f in os.listdir(dir_path) if f.endswith(('.png', '.jpg', '.jpeg'))] + + print(f"处理 {direction} 方向的 {len(image_files)} 个图像...") + + # 处理每个图像 + for img_file in tqdm(image_files): + img_path = os.path.join(dir_path, img_file) + + # 读取图像 + img = cv2.imread(img_path) + if img is None: + print(f"错误: 无法加载图像 '{img_path}'") + continue + + # 开始计时 + start_time = time.time() + + # 检测箭头方向 + detected_direction = detect_arrow_direction(img) + + # 结束计时 + end_time = time.time() + processing_time = end_time - start_time + + # 确定检测是否正确 + is_correct = detected_direction == direction + + # 保存可视化结果 + result_filename = f"{direction}_{img_file.split('.')[0]}_result.jpg" + result_path = os.path.join(save_dir, result_filename) + # visualize_arrow_detection(img, result_path) + + # 保存结果 + results.append({ + "图像文件": img_file, + "真实方向": direction, + "检测方向": detected_direction, + "是否正确": is_correct, + "处理时间(秒)": processing_time, + "结果文件": result_filename + }) + + # 创建结果DataFrame + results_df = pd.DataFrame(results) + + # 保存结果到CSV + csv_path = os.path.join(save_dir, "arrow_detection_results.csv") + results_df.to_csv(csv_path, index=False, encoding='utf-8-sig') + + # 生成统计报告 + generate_report(results_df, save_dir) + + return results_df + +def generate_report(results_df, save_dir): + """生成统计报告和可视化""" + # 计算总体准确率 + accuracy = results_df["是否正确"].mean() * 100 + + # 按箭头方向分组计算准确率 + direction_accuracy = results_df.groupby("真实方向")["是否正确"].mean() * 100 + + # 计算平均处理时间 + avg_time = results_df["处理时间(秒)"].mean() * 1000 # 转换为毫秒 + + # 创建报告文件 + report_path = os.path.join(save_dir, "arrow_detection_report.txt") + + with open(report_path, "w", encoding="utf-8") as f: + f.write("箭头方向检测 - 测试报告\n") + f.write("=======================\n\n") + f.write(f"测试日期: {time.strftime('%Y-%m-%d %H:%M:%S')}\n") + f.write(f"测试图像总数: {len(results_df)}\n\n") + f.write(f"总体准确率: {accuracy:.2f}%\n") + f.write("各方向准确率:\n") + for direction, acc in direction_accuracy.items(): + f.write(f" - {direction}: {acc:.2f}%\n") + f.write(f"\n平均处理时间: {avg_time:.2f} 毫秒\n\n") + + # 错误案例分析 + if not results_df["是否正确"].all(): + f.write("错误检测案例:\n") + error_cases = results_df[~results_df["是否正确"]] + for _, row in error_cases.iterrows(): + f.write(f" - 文件: {row['图像文件']}, 真实方向: {row['真实方向']}, 错误检测为: {row['检测方向']}\n") + + # 创建可视化图表 + plt.figure(figsize=(12, 6)) + + # 准确率条形图 + plt.subplot(1, 2, 1) + # 将中文索引转为英文避免字体问题 + direction_accuracy_en = direction_accuracy.copy() + direction_accuracy_en.index = direction_accuracy.index.map(lambda x: "Left" if x == "left" else "Right") + direction_accuracy_en.plot(kind='bar', color=['blue', 'green']) + plt.title('各方向检测准确率') + plt.ylabel('准确率 (%)') + plt.ylim(0, 100) + plt.grid(True, linestyle='--', alpha=0.7) + + # 处理时间箱线图 + plt.subplot(1, 2, 2) + # 将中文列名改为英文再制图,避免字体问题 + temp_df = results_df.copy() + temp_df.rename(columns={"处理时间(秒)": "processing_time", "真实方向": "direction"}, inplace=True) + temp_df.boxplot(column=['processing_time'], by='direction') + plt.title('处理时间分布') + plt.ylabel('时间 (秒)') + plt.suptitle('') + + # 保存图表 + plt.tight_layout() + plt.savefig(os.path.join(save_dir, "arrow_detection_stats.png")) + + print(f"测试报告已保存到: {report_path}") + print(f"统计图表已保存到: {os.path.join(save_dir, 'arrow_detection_stats.png')}") + +def main(): + # 创建参数解析器 + parser = argparse.ArgumentParser(description='箭头方向检测批量测试') + parser.add_argument('--data-dir', default="res/arrows", + help='箭头图像数据目录 (默认: res/arrows)') + parser.add_argument('--save-dir', default="res/arrows/test", + help='保存结果的目录 (默认: res/arrows/test)') + parser.add_argument('--show', action='store_true', + help='显示结果图像') + + args = parser.parse_args() + + # 运行批量测试 + results = batch_test_arrows(args.data_dir, args.save_dir, args.show) + + # 输出总体结果 + correct = results["是否正确"].sum() + total = len(results) + print(f"\n测试完成! 总共测试了 {total} 张图像,正确检测了 {correct} 张") + print(f"总体准确率: {(correct/total*100):.2f}%") + + # 按真实方向打印准确率 + for direction in ["left", "right"]: + dir_results = results[results["真实方向"] == direction] + if len(dir_results) > 0: + dir_correct = dir_results["是否正确"].sum() + dir_total = len(dir_results) + print(f"{direction} 方向准确率: {(dir_correct/dir_total*100):.2f}% ({dir_correct}/{dir_total})") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/test/task-arrow/test_arrow.py b/test/task-arrow/test_arrow.py index 88286ba..36aa1f3 100644 --- a/test/task-arrow/test_arrow.py +++ b/test/task-arrow/test_arrow.py @@ -1,22 +1,31 @@ #!/usr/bin/env python3 -import cv2 -import sys import os +import sys +import cv2 +import argparse -# 添加父目录到路径,以便能够导入utils -sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +# 添加项目根目录到路径 +current_dir = os.path.dirname(os.path.abspath(__file__)) +project_root = os.path.dirname(os.path.dirname(current_dir)) +sys.path.append(project_root) from utils.decode_arrow import detect_arrow_direction, visualize_arrow_detection def main(): - # 检查命令行参数 - if len(sys.argv) < 2: - print("使用方法: python test_arrow.py <图像路径>") - sys.exit(1) + # 创建参数解析器 + parser = argparse.ArgumentParser(description='箭头方向检测测试') + parser.add_argument('--image', default="res/arrows/left/image-3.png", + help='图像文件路径 (默认: image_20250511_121219.png)') + parser.add_argument('--save', default="res/arrows/test/arrow_detection_result.jpg", + help='保存可视化结果的路径 (默认: res/arrows/test/arrow_detection_result.jpg)') + parser.add_argument('--show', action='store_true', + help='显示可视化结果') + + args = parser.parse_args() # 获取图像路径 - image_path = sys.argv[1] + image_path = args.image # 检查文件是否存在 if not os.path.exists(image_path): @@ -25,12 +34,31 @@ def main(): print(f"正在处理图像: {image_path}") - # 检测箭头方向 - direction = detect_arrow_direction(image_path) - print(f"检测到的箭头方向: {direction}") + # 加载图像 + img = cv2.imread(image_path) + if img is None: + print(f"错误: 无法加载图像 '{image_path}'") + sys.exit(1) - # 可视化检测过程 - visualize_arrow_detection(image_path) + try: + # 检测箭头方向 + direction = detect_arrow_direction(img, observe=True) + print(f"检测到的箭头方向: {direction}") + + # 可视化检测过程并保存结果 + visualize_arrow_detection(img, args.save) + + print(f"可视化结果已保存到: {args.save}") + + # 如果需要显示结果,等待用户按键 + print("按任意键退出...") + cv2.waitKey(0) + except Exception as e: + print(f"发生错误: {e}") + finally: + cv2.destroyAllWindows() + + return direction if __name__ == "__main__": - main() \ No newline at end of file + main() \ No newline at end of file diff --git a/test/task-arrow/test_arrow_with_image.py b/test/task-arrow/test_arrow_with_image.py deleted file mode 100644 index cdc97ac..0000000 --- a/test/task-arrow/test_arrow_with_image.py +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env python3 - -import os -import sys -import cv2 -import numpy as np -import argparse - -# 添加父目录到路径,以便能够导入utils -sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) - -from utils.decode_arrow import detect_arrow_direction, visualize_arrow_detection - -def main(): - # 创建参数解析器 - parser = argparse.ArgumentParser(description='测试箭头方向检测') - parser.add_argument('image_path', help='图像文件路径') - parser.add_argument('--save', help='保存可视化结果的路径', default=None) - parser.add_argument('--show', help='显示可视化结果', action='store_true') - parser.add_argument('--debug', help='输出详细的调试信息', action='store_true') - - args = parser.parse_args() - - # 检查文件是否存在 - if not os.path.exists(args.image_path): - print(f"错误: 文件 '{args.image_path}' 不存在") - sys.exit(1) - - print(f"正在处理图像: {args.image_path}") - - # 加载图像 - img = cv2.imread(args.image_path) - if img is None: - print(f"错误: 无法加载图像 '{args.image_path}'") - sys.exit(1) - - # 如果需要,显示原始图像 - if args.debug: - cv2.imshow('Original Image', img) - cv2.waitKey(0) - - # 检测箭头方向 - direction = detect_arrow_direction(img) - print(f"检测到的箭头方向: {direction}") - - # 如果需要,显示可视化结果 - if args.show or args.save: - visualize_arrow_detection(img, args.save) - - # 如果不需要显示可视化结果但需要保存 - if args.save and not args.show: - print(f"可视化结果已保存到: {args.save}") - - return direction - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/test/task-arrow/test_right_arrow.py b/test/task-arrow/test_right_arrow.py deleted file mode 100644 index 4b0579c..0000000 --- a/test/task-arrow/test_right_arrow.py +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env python3 - -import os -import sys -import cv2 -import argparse - -# 添加项目根目录到路径 -current_dir = os.path.dirname(os.path.abspath(__file__)) -project_root = os.path.dirname(os.path.dirname(current_dir)) -sys.path.append(project_root) - -from utils.decode_arrow import detect_arrow_direction, visualize_arrow_detection - -def main(): - # 创建参数解析器 - parser = argparse.ArgumentParser(description='箭头方向检测测试') - parser.add_argument('--image', default="res/arrows/left/image-2.png", - help='图像文件路径 (默认: image_20250511_121219.png)') - parser.add_argument('--save', default="res/arrows/test/arrow_detection_result.jpg", - help='保存可视化结果的路径 (默认: res/arrows/test/arrow_detection_result.jpg)') - parser.add_argument('--show', action='store_true', - help='显示可视化结果') - - args = parser.parse_args() - - # 获取图像路径 - image_path = args.image - - # 检查文件是否存在 - if not os.path.exists(image_path): - print(f"错误: 文件 '{image_path}' 不存在") - sys.exit(1) - - print(f"正在处理图像: {image_path}") - - # 加载图像 - img = cv2.imread(image_path) - if img is None: - print(f"错误: 无法加载图像 '{image_path}'") - sys.exit(1) - - # 检测箭头方向 - direction = detect_arrow_direction(img) - print(f"检测到的箭头方向: {direction}") - - # 可视化检测过程并保存结果 - visualize_arrow_detection(img, args.save) - - print(f"可视化结果已保存到: {args.save}") - - # 如果需要显示结果,等待用户按键 - if args.show: - print("按任意键退出...") - cv2.waitKey(0) - cv2.destroyAllWindows() - - return direction - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/utils/decode_arrow.py b/utils/decode_arrow.py index b0df849..1f493be 100644 --- a/utils/decode_arrow.py +++ b/utils/decode_arrow.py @@ -1,12 +1,14 @@ import cv2 import numpy as np -def detect_arrow_direction(image): +def detect_arrow_direction(image, observe=False, delay=500): """ 从图像中提取绿色箭头并判断其指向方向(左或右) 参数: image: 输入图像,可以是文件路径或者已加载的图像数组 + observe: 是否输出中间状态信息和可视化结果,默认为False + delay: 展示每个步骤的等待时间(毫秒),默认为500ms 返回: direction: 字符串,"left"表示左箭头,"right"表示右箭头,"unknown"表示无法确定 @@ -21,9 +23,19 @@ def detect_arrow_direction(image): 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]) @@ -32,9 +44,19 @@ def detect_arrow_direction(image): # 创建绿色的掩码 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() @@ -43,13 +65,91 @@ def detect_arrow_direction(image): # 如果没有找到轮廓,返回未知 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) + # 获取轮廓的最小外接矩形 - x, y, w, h = cv2.boundingRect(max_contour) + 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) @@ -59,35 +159,159 @@ def detect_arrow_direction(image): cx = int(M["m10"] / M["m00"]) cy = int(M["m01"] / M["m00"]) else: - cx, cy = x + w//2, y + h//2 + cx, cy = int(center[0]), int(center[1]) - # 将图像分为左右两部分 - left_region = gray[y:y+h, x:cx] - right_region = gray[y:y+h, cx:x+w] + 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) - # 计算每个区域中白色像素的数量(箭头部分) - left_pixels = cv2.countNonZero(left_region) - right_pixels = cv2.countNonZero(right_region) + # 改进的箭头尖端检测算法 + # 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 + }) - # 计算每个区域中白色像素的密度 - left_density = left_pixels / (left_region.size + 1e-10) - right_density = right_pixels / (right_region.size + 1e-10) - - # 根据左右区域的像素密度确定箭头方向 - # 如果箭头指向右侧,右侧区域的箭头头部应该有更多的像素密度 - # 如果箭头指向左侧,左侧区域的箭头头部应该有更多的像素密度 - if right_density > left_density: - return "right" + # 没有缺陷点,使用矩形判断 + if not defect_points: + # 判断逻辑将在后面处理 + arrow_tip = None else: - return "left" + # 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): +def visualize_arrow_detection(image, save_path=None, observe=False, delay=500): """ 可视化箭头检测过程,显示中间结果 参数: image: 输入图像,可以是文件路径或者已加载的图像数组 save_path: 保存结果图像的路径(可选) + observe: 是否输出中间状态信息和可视化结果,默认为False + delay: 展示每个步骤的等待时间(毫秒),默认为500ms """ # 如果输入是字符串(文件路径),则加载图像 if isinstance(image, str): @@ -99,6 +323,9 @@ def visualize_arrow_detection(image, save_path=None): print("无法加载图像") return + if observe: + print("\n开始可视化箭头检测过程") + # 转换到HSV颜色空间 hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) @@ -124,8 +351,10 @@ def visualize_arrow_detection(image, save_path=None): cv2.drawContours(output, [max_contour], -1, (0, 0, 255), 2) # 获取轮廓的最小外接矩形 - x, y, w, h = cv2.boundingRect(max_contour) - cv2.rectangle(output, (x, y), (x + w, y + h), (255, 0, 0), 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) @@ -137,12 +366,24 @@ def visualize_arrow_detection(image, save_path=None): # 绘制中心点 cv2.circle(output, (cx, cy), 5, (255, 0, 0), -1) - - # 绘制分割线 - cv2.line(output, (cx, y), (cx, y + h), (0, 255, 255), 2) + + # 绘制凸包 + 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) + direction = detect_arrow_direction(img, observe=observe, delay=delay) # 在图像上添加方向文本 cv2.putText(output, f"Direction: {direction}", (10, 30), @@ -151,6 +392,8 @@ def visualize_arrow_detection(image, save_path=None): # 如果提供了保存路径,保存结果图像 if save_path: cv2.imwrite(save_path, output) + if observe: + print(f"结果已保存到: {save_path}") # 创建一个包含所有图像的窗口 result = np.hstack((img, green_only, output)) @@ -163,18 +406,18 @@ def visualize_arrow_detection(image, save_path=None): resized = cv2.resize(result, dim, interpolation=cv2.INTER_AREA) # 显示结果 - # cv2.imshow('Arrow Detection Process', resized) - # cv2.waitKey(0) - # cv2.destroyAllWindows() + cv2.imshow('Arrow Detection Process', resized) + cv2.waitKey(0) + cv2.destroyAllWindows() # 用法示例 if __name__ == "__main__": # 替换为实际图像路径 image_path = "path/to/arrow/image.png" - # 检测箭头方向 - direction = detect_arrow_direction(image_path) + # 检测箭头方向,使用较长的延迟时间(1500毫秒) + direction = detect_arrow_direction(image_path, observe=True, delay=1500) print(f"检测到的箭头方向: {direction}") - # 可视化检测过程 - visualize_arrow_detection(image_path) + # 可视化检测过程,使用较长的延迟时间 + visualize_arrow_detection(image_path, observe=True, delay=1500)