diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..1870410 Binary files /dev/null and b/.DS_Store differ diff --git a/README.md b/README.md index f90d146..eec9a61 100644 --- a/README.md +++ b/README.md @@ -1,56 +1,113 @@ -# 任务模块 +# 黄色赛道检测与距离估算 -## top +这个项目提供了一套用于检测黄色赛道并估算距离的工具。基于OpenCV的计算机视觉技术,能够从图像中识别黄色赛道,计算方向角度,并估算摄像机到赛道前方的距离。 -1. “装货 / qrcode” -2. 曲线赛道 -3. 上下坡 - - 存在两种可能性 -4. 石板道 -5. 栅栏 / 栏杆 +## 功能特点 -## Task-1:出生点 - 取货 +- 黄色赛道检测与分割 +- 赛道方向和角度计算 +- 赛道距离估算 +- 结果可视化展示 +- 提供易于使用的测试工具 -![alt text](./res/readme/image.png) +## 安装依赖 -从二维码取货完,然后走到弯道入口。 +确保已安装以下依赖库: -![alt text](./res/readme/image-1.png) +```bash +pip install opencv-python numpy +``` -## Task-2:弯道任务 -过去,以及回来。两个方向都需要。 +## 使用方法 -![alt text](./res/readme/image-2.png) -![alt text](./res/readme/image-3.png) +### 基本用法 -## Task-2.5:识别 -> 根据箭头决定方向。 +```python +from utils.detect_track import detect_yellow_track, visualize_track_detection -这里采取传统cv的基于凸包计算的算法。 +# 检测赛道并估算距离 +image_path = "path/to/your/image.jpg" +distance, path_info = detect_yellow_track(image_path) -## Task-3:上下坡 -同样需要考虑两个方向。(正式计时各有一半的概率) -> 感觉两个坡度稍微有一点点区别。 +if distance is not None: + print(f"估算距离: {distance:.2f}米") + print(f"赛道角度: {path_info['track_angle']:.2f}°") + print(f"转向方向: {path_info['turn_direction']}") -![alt text](./res/readme/image-4.png) -![alt text](./res/readme/image-5.png) +# 可视化检测过程 +visualize_track_detection(image_path, save_path="result.jpg") +``` -以及反方向。 +### 使用测试脚本 -## Task-4:石板路 +项目提供了一个方便的测试脚本: -![alt text](./res/readme/image-6.png) -![alt text](./res/readme/image-7.png) +```bash +python test_track_detection.py --image path/to/your/image.jpg --observe --save result.jpg +``` -## Task-5:过栅栏 -![alt text](./res/readme/image-8.png) +参数说明: +- `--image`: 要处理的图像路径(必需) +- `--save`: 结果图像保存路径(可选) +- `--observe`: 显示处理步骤的中间结果 +- `--delay`: 中间步骤显示的延迟时间,单位为毫秒(默认500ms) -## Task-5.5:走向卸货 -从上一个赛道结束到 B 二维码。 +## 技术细节 -## Task-6:卸货 -也可能在另一边。 -这里是感觉在走过去的过程中就能判断二维码。 +### 检测原理 -![alt text](./res/readme/image-9.png) -![alt text](./res/readme/image-10.png) \ No newline at end of file +1. 将图像转换为HSV颜色空间 +2. 使用颜色阈值提取黄色区域 +3. 寻找轮廓并合并相关区域 +4. 计算赛道的上部和下部中心点 +5. 基于中心点计算方向角度 +6. 根据黄色区域在图像中的占比估算距离 + +### 距离估算 + +距离估算基于以下假设: +- 黄色赛道在图像中的占比与实际距离有反比关系 +- 占比越大,距离越近;占比越小,距离越远 + +注意:实际应用中,建议根据相机参数和实际测量进行标定,以获得更准确的结果。 + +### 函数说明 + +- `preprocess_image(image)`: 预处理图像,进行边缘检测 +- `detect_yellow_track(image, observe=False, delay=500)`: 检测黄色赛道并估算距离 +- `visualize_track_detection(image, save_path=None, observe=False, delay=500)`: 可视化检测过程 +- `estimate_distance_to_track(image)`: 估算到赛道的距离(简化版API) + +## 调整参数 + +如果检测效果不理想,可以尝试调整以下参数: + +1. HSV颜色范围(针对不同光照条件): +```python +lower_yellow = np.array([20, 100, 100]) # 调整黄色的下限 +upper_yellow = np.array([30, 255, 255]) # 调整黄色的上限 +``` + +2. 轮廓近似参数: +```python +epsilon = 0.01 * cv2.arcLength(all_contours, False) # 调整系数(0.01) +``` + +3. 距离估算系数: +```python +normalized_distance = min(10.0, max(0.0, estimated_distance / 100.0)) # 调整系数(100.0) +``` + +## 示例结果 + +处理后的图像将包含以下信息: +- 红色点:表示赛道的顶部和底部中心点 +- 绿色线:表示赛道的中心线 +- 文本信息:距离、角度和方向 + +## 注意事项 + +- 该方法在光照条件稳定的环境中效果最佳 +- 黄色赛道需要与背景有足够的对比度 +- 摄像机应保持相对稳定的高度和角度 +- 实际应用中需要根据具体场景调整参数 \ No newline at end of file diff --git a/res/.DS_Store b/res/.DS_Store new file mode 100644 index 0000000..f5d0f1d Binary files /dev/null and b/res/.DS_Store differ diff --git a/res/path/image_20250513_162556.png b/res/path/image_20250513_162556.png new file mode 100644 index 0000000..1575443 Binary files /dev/null and b/res/path/image_20250513_162556.png differ diff --git a/res/path/image_20250514_024313.png b/res/path/image_20250514_024313.png new file mode 100644 index 0000000..a2cf782 Binary files /dev/null and b/res/path/image_20250514_024313.png differ diff --git a/res/path/image_20250514_024347.png b/res/path/image_20250514_024347.png new file mode 100644 index 0000000..f9d3b16 Binary files /dev/null and b/res/path/image_20250514_024347.png differ diff --git a/res/path/test/result_image_20250513_162556.png/result_image_20250513_162556.png b/res/path/test/result_image_20250513_162556.png/result_image_20250513_162556.png new file mode 100644 index 0000000..9394635 Binary files /dev/null and b/res/path/test/result_image_20250513_162556.png/result_image_20250513_162556.png differ diff --git a/test/task-path-track/README_yellow_track.md b/test/task-path-track/README_yellow_track.md new file mode 100644 index 0000000..3eff5c1 --- /dev/null +++ b/test/task-path-track/README_yellow_track.md @@ -0,0 +1,113 @@ +# 黄色赛道检测与距离估算 + +这个模块提供了一套用于检测黄色赛道并估算距离的工具。基于OpenCV的计算机视觉技术,能够从图像中识别黄色赛道,计算方向角度,并估算摄像机到赛道前方的距离。 + +## 功能特点 + +- 黄色赛道检测与分割 +- 赛道方向和角度计算 +- 赛道距离估算 +- 结果可视化展示 +- 提供易于使用的测试工具 + +## 安装依赖 + +确保已安装以下依赖库: + +```bash +pip install opencv-python numpy +``` + +## 使用方法 + +### 基本用法 + +```python +from utils.detect_track import detect_yellow_track, visualize_track_detection + +# 检测赛道并估算距离 +image_path = "path/to/your/image.jpg" +distance, path_info = detect_yellow_track(image_path) + +if distance is not None: + print(f"估算距离: {distance:.2f}米") + print(f"赛道角度: {path_info['track_angle']:.2f}°") + print(f"转向方向: {path_info['turn_direction']}") + +# 可视化检测过程 +visualize_track_detection(image_path, save_path="result.jpg") +``` + +### 使用测试脚本 + +项目提供了一个方便的测试脚本: + +```bash +python test_track_detection.py --image path/to/your/image.jpg --observe --save result.jpg +``` + +参数说明: +- `--image`: 要处理的图像路径(必需) +- `--save`: 结果图像保存路径(可选) +- `--observe`: 显示处理步骤的中间结果 +- `--delay`: 中间步骤显示的延迟时间,单位为毫秒(默认500ms) + +## 技术细节 + +### 检测原理 + +1. 将图像转换为HSV颜色空间 +2. 使用颜色阈值提取黄色区域 +3. 寻找轮廓并合并相关区域 +4. 计算赛道的上部和下部中心点 +5. 基于中心点计算方向角度 +6. 根据黄色区域在图像中的占比估算距离 + +### 距离估算 + +距离估算基于以下假设: +- 黄色赛道在图像中的占比与实际距离有反比关系 +- 占比越大,距离越近;占比越小,距离越远 + +注意:实际应用中,建议根据相机参数和实际测量进行标定,以获得更准确的结果。 + +### 函数说明 + +- `preprocess_image(image)`: 预处理图像,进行边缘检测 +- `detect_yellow_track(image, observe=False, delay=500)`: 检测黄色赛道并估算距离 +- `visualize_track_detection(image, save_path=None, observe=False, delay=500)`: 可视化检测过程 +- `estimate_distance_to_track(image)`: 估算到赛道的距离(简化版API) + +## 调整参数 + +如果检测效果不理想,可以尝试调整以下参数: + +1. HSV颜色范围(针对不同光照条件): +```python +lower_yellow = np.array([20, 100, 100]) # 调整黄色的下限 +upper_yellow = np.array([30, 255, 255]) # 调整黄色的上限 +``` + +2. 轮廓近似参数: +```python +epsilon = 0.01 * cv2.arcLength(all_contours, False) # 调整系数(0.01) +``` + +3. 距离估算系数: +```python +normalized_distance = min(10.0, max(0.0, estimated_distance / 100.0)) # 调整系数(100.0) +``` + +## 示例结果 + +处理后的图像将包含以下信息: +- 红色点:表示赛道的顶部和底部中心点 +- 绿色线:表示赛道的中心线 +- 文本信息:距离、角度和方向 + +## 注意事项 + +- 该方法在光照条件稳定的环境中效果最佳 +- 黄色赛道需要与背景有足够的对比度 +- 摄像机应保持相对稳定的高度和角度 +- 实际应用中需要根据具体场景调整参数 \ No newline at end of file diff --git a/test/task-path-track/tempCodeRunnerFile.py b/test/task-path-track/tempCodeRunnerFile.py new file mode 100644 index 0000000..4791ed5 --- /dev/null +++ b/test/task-path-track/tempCodeRunnerFile.py @@ -0,0 +1 @@ +True \ No newline at end of file diff --git a/test/task-path-track/yellow_track_demo.py b/test/task-path-track/yellow_track_demo.py new file mode 100644 index 0000000..6e27a57 --- /dev/null +++ b/test/task-path-track/yellow_track_demo.py @@ -0,0 +1,178 @@ +import cv2 +import os +import sys +import time +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.detect_track import detect_yellow_track, visualize_track_detection, detect_horizontal_track_edge, visualize_horizontal_track_edge + +def process_image(image_path, save_dir=None, show_steps=False): + """处理单张图像""" + print(f"处理图像: {image_path}") + + # 检测赛道并估算距离 + start_time = time.time() + edge_point, edge_info = detect_horizontal_track_edge(image_path, observe=show_steps) + processing_time = time.time() - start_time + + # 输出结果 + if edge_point is not None and edge_info is not None: + print(f"处理时间: {processing_time:.3f}秒") + print(f"边缘点: ({edge_point[0]}, {edge_point[1]})") + print(f"到中线距离: {edge_info['distance_to_center']}像素") + print(f"边缘斜率: {edge_info['slope']:.4f}") + print(f"是否水平: {edge_info['is_horizontal']}") + print(f"点数量: {edge_info['points_count']}") + print(f"中线交点: ({edge_info['intersection_point'][0]}, {edge_info['intersection_point'][1]})") + print(f"交点到底部距离: {edge_info['distance_to_bottom']:.1f}像素") + print(f"注意: 中线交点是垂直中线与边缘横线的交点") + print("-" * 30) + + # 如果指定了保存目录,保存结果 + if save_dir: + if not os.path.exists(save_dir): + os.makedirs(save_dir) + + base_name = os.path.basename(image_path) + save_path = os.path.join(save_dir, f"result_{base_name}") + + # 可视化并保存 + visualize_horizontal_track_edge(image_path, save_path=save_path, observe=show_steps) + print(f"结果已保存到: {save_path}") + else: + print("未能检测到黄色赛道") + + return edge_point, edge_info + +def process_video(video_path, save_path=None, show_output=True): + """处理视频""" + print(f"处理视频: {video_path}") + + # 打开视频文件 + cap = cv2.VideoCapture(video_path) + if not cap.isOpened(): + print("无法打开视频文件") + return + + # 获取视频基本信息 + fps = cap.get(cv2.CAP_PROP_FPS) + width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) + height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) + total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) + + print(f"视频信息: {width}x{height}, {fps} FPS, 总帧数: {total_frames}") + + # 如果需要保存,创建VideoWriter + if save_path: + fourcc = cv2.VideoWriter_fourcc(*'XVID') + out = cv2.VideoWriter(save_path, fourcc, fps, (width, height)) + + # 处理计数器 + frame_count = 0 + processed_count = 0 + start_time = time.time() + + while True: + ret, frame = cap.read() + if not ret: + break + + frame_count += 1 + + # 每5帧处理一次以提高性能 + if frame_count % 5 == 0: + processed_count += 1 + + # 检测赛道 + edge_point, edge_info = detect_horizontal_track_edge(frame, observe=False) + + # 创建结果图像(使用visualize_horizontal_track_edge函数) + if edge_point is not None and edge_info is not None: + result_frame = visualize_horizontal_track_edge(frame, observe=False) + else: + # 如果未检测到赛道,显示原始帧并添加警告 + result_frame = frame.copy() + cv2.putText(result_frame, "未检测到横向赛道", (width//4, height//2), + cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 0, 255), 2) + + # 添加帧计数 + cv2.putText(result_frame, f"帧: {frame_count}/{total_frames}", + (width - 200, height - 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1) + + # 保存或显示结果 + if save_path: + out.write(result_frame) + + if show_output: + # 将交点信息显示在窗口标题上 + if edge_point is not None and edge_info is not None: + intersection_point = edge_info['intersection_point'] + distance_to_bottom = edge_info['distance_to_bottom'] + title = f'赛道检测 - 交点:({intersection_point[0]},{intersection_point[1]}) 底距:{distance_to_bottom:.1f}px' + else: + title = '赛道检测 - 未检测到交点' + + cv2.imshow(title, result_frame) + key = cv2.waitKey(1) & 0xFF + if key == 27: # ESC键退出 + break + + # 每秒更新一次处理进度 + if frame_count % int(fps) == 0: + elapsed = time.time() - start_time + percent = frame_count / total_frames * 100 + print(f"进度: {percent:.1f}% ({frame_count}/{total_frames}), 已用时间: {elapsed:.1f}秒") + + # 清理 + cap.release() + if save_path: + out.release() + cv2.destroyAllWindows() + + # 输出统计信息 + total_time = time.time() - start_time + print(f"视频处理完成,总时间: {total_time:.2f}秒") + print(f"实际处理帧数: {processed_count}/{frame_count}") + if processed_count > 0: + print(f"平均每帧处理时间: {total_time/processed_count:.3f}秒") + +def main(): + parser = argparse.ArgumentParser(description='黄色赛道检测演示程序') + parser.add_argument('--input', type=str, default='res/path/image_20250513_162556.png', help='输入图像或视频的路径') + parser.add_argument('--output', type=str, default='res/path/test/result_image_20250513_162556.png', help='输出结果的保存路径') + parser.add_argument('--type', type=str, choices=['image', 'video'], help='输入类型,不指定会自动检测') + parser.add_argument('--show', default=True, action='store_true', help='显示处理步骤') + + args = parser.parse_args() + + # 检查输入路径 + if not os.path.exists(args.input): + print(f"错误:文件 '{args.input}' 不存在") + return + + # 如果未指定类型,根据文件扩展名判断 + if args.type is None: + ext = os.path.splitext(args.input)[1].lower() + if ext in ['.jpg', '.jpeg', '.png', '.bmp']: + args.type = 'image' + elif ext in ['.mp4', '.avi', '.mov']: + args.type = 'video' + else: + print(f"错误:无法确定文件类型 '{ext}'") + return + + # 根据类型处理 + if args.type == 'image': + # 获取输出目录 + output_dir = os.path.dirname(args.output) + process_image(args.input, output_dir, args.show) + else: # video + process_video(args.input, args.output, args.show) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/test_track_detection.py b/test_track_detection.py new file mode 100644 index 0000000..1681bc3 --- /dev/null +++ b/test_track_detection.py @@ -0,0 +1,38 @@ +import cv2 +import argparse +from utils.detect_track import detect_yellow_track, visualize_track_detection + +def main(): + # 创建命令行参数解析器 + parser = argparse.ArgumentParser(description='黄色赛道检测测试程序') + parser.add_argument('--image', type=str, required=True, help='要处理的图像路径') + parser.add_argument('--save', type=str, help='保存结果图像的路径(可选)') + parser.add_argument('--observe', action='store_true', help='显示中间处理步骤') + parser.add_argument('--delay', type=int, default=500, help='步骤间延迟时间(毫秒)') + + # 解析命令行参数 + args = parser.parse_args() + + # 检测赛道并估算距离 + print("正在处理图像...") + distance, path_info = detect_yellow_track(args.image, observe=args.observe, delay=args.delay) + + if distance is not None and path_info is not None: + print("\n===== 赛道分析结果 =====") + print(f"估算距离: {distance:.2f}米") + print(f"赛道角度: {path_info['track_angle']:.2f}°") + print(f"转向方向: {path_info['turn_direction']}") + print(f"赛道是否笔直: {'是' if path_info['is_straight'] else '否'}") + print(f"黄色区域占比: {path_info['area_ratio']:.4f}") + print("========================\n") + else: + print("未能检测到黄色赛道,请检查图像或调整参数") + + # 可视化检测过程 + print("开始可视化检测过程...") + visualize_track_detection(args.image, save_path=args.save, observe=args.observe, delay=args.delay) + + print("处理完成") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/utils/detect_track.py b/utils/detect_track.py new file mode 100644 index 0000000..c6ceb31 --- /dev/null +++ b/utils/detect_track.py @@ -0,0 +1,1057 @@ +import cv2 +import numpy as np +from sklearn.cluster import KMeans +from sklearn.metrics import silhouette_score + +def preprocess_image(image): + """ + 预处理图像以便进行赛道检测 + + 参数: + image: 输入图像,可以是文件路径或者已加载的图像数组 + + 返回: + edges: 处理后的边缘图像 + """ + # 如果输入是字符串(文件路径),则加载图像 + if isinstance(image, str): + img = cv2.imread(image) + else: + img = image.copy() + + if img is None: + print("无法加载图像") + return None + + # 使用提供的预处理步骤 + gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + blurred = cv2.GaussianBlur(gray, (5, 5), 0) + edges = cv2.Canny(blurred, 50, 150) # 调整阈值以适应不同光照条件 + + cv2.imshow("edges", edges) + key = cv2.waitKey(0) + if key == ord('q'): # 按q键退出 + cv2.destroyAllWindows() + else: + cv2.destroyAllWindows() + + return edges + +def detect_yellow_track(image, observe=False, delay=1500): + """ + 从图像中提取黄色赛道并估算距离 + + 参数: + image: 输入图像,可以是文件路径或者已加载的图像数组 + observe: 是否输出中间状态信息和可视化结果,默认为False + delay: 展示每个步骤的等待时间(毫秒),默认为1500ms + + 返回: + distance: 估算的赛道到摄像机的距离 + path_info: 赛道路径信息字典 + """ + # 如果输入是字符串(文件路径),则加载图像 + if isinstance(image, str): + img = cv2.imread(image) + else: + img = image.copy() + + if img is None: + print("无法加载图像") + return None, None + + 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_yellow = np.array([20, 100, 100]) + upper_yellow = np.array([30, 255, 255]) + + # 创建黄色的掩码 + mask = cv2.inRange(hsv, lower_yellow, upper_yellow) + + if observe: + print("步骤3: 创建黄色掩码") + cv2.imshow("黄色掩码", mask) + cv2.waitKey(delay) + + # 应用掩码,只保留黄色部分 + yellow_only = cv2.bitwise_and(img, img, mask=mask) + + if observe: + print("步骤4: 提取黄色部分") + cv2.imshow("只保留黄色", yellow_only) + cv2.waitKey(delay) + + # 将掩码转为灰度图 + gray = mask.copy() + + # 查找轮廓 + contours, _ = cv2.findContours(gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + + # 如果没有找到轮廓,返回None + if not contours: + if observe: + print("未找到轮廓") + return None, None + + 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) + + # 获取图像尺寸 + height, width = img.shape[:2] + + # 在图像上绘制三条竖线并计算与轮廓的交点 + vertical_lines = [ + width // 2, # 中线 + int(width * 2 / 5), # 2/5位置的线 + int(width * 3 / 5) # 3/5位置的线 + ] + + intersection_points = [] + vertical_line_images = [] + + if observe: + print("步骤5.1: 绘制三条竖线并计算与轮廓的交点") + + for i, x in enumerate(vertical_lines): + # 为每条竖线创建单独的图像用于可视化 + line_img = img.copy() + cv2.line(line_img, (x, 0), (x, height), (0, 0, 255), 2) + + # 记录每条线与轮廓的交点 + line_intersections = [] + + # 对于每个轮廓 + for contour in contours: + # 遍历轮廓中的所有点对 + for j in range(len(contour) - 1): + pt1 = contour[j][0] + pt2 = contour[j+1][0] + + # 检查两点是否在竖线两侧 + if (pt1[0] <= x and pt2[0] >= x) or (pt1[0] >= x and pt2[0] <= x): + # 计算交点的y坐标(线性插值) + if pt2[0] == pt1[0]: # 避免除以零 + y_intersect = pt1[1] + else: + slope = (pt2[1] - pt1[1]) / (pt2[0] - pt1[0]) + y_intersect = pt1[1] + slope * (x - pt1[0]) + + # 添加交点 + line_intersections.append((x, int(y_intersect))) + + # 在每条线上标记所有交点 + for point in line_intersections: + cv2.circle(line_img, point, 5, (255, 0, 0), -1) + + vertical_line_images.append(line_img) + + # 寻找最底部的交点(y坐标最大) + if line_intersections: + bottom_most = max(line_intersections, key=lambda p: p[1]) + intersection_points.append(bottom_most) + else: + intersection_points.append(None) + + if observe: + for i, line_img in enumerate(vertical_line_images): + cv2.imshow(f"竖线 {i+1} 与轮廓的交点", line_img) + cv2.waitKey(delay) + + # 找出底部最近的点(y坐标最大的点) + valid_intersections = [p for p in intersection_points if p is not None] + + if not valid_intersections: + if observe: + print("未找到任何竖线与轮廓的交点") + return None, None + + bottom_most_point = max(valid_intersections, key=lambda p: p[1]) + bottom_most_index = intersection_points.index(bottom_most_point) + + # 计算目标线的斜率和中线距离 + target_line_x = vertical_lines[bottom_most_index] + center_line_x = vertical_lines[0] # 中线x坐标 + + # 计算目标线到中线的距离(正值表示在右侧,负值表示在左侧) + distance_to_center = target_line_x - center_line_x + + # 计算目标线的斜率 + # 为了计算斜率,我们需要在目标线上找到两个点 + # 首先找到与目标线相同轮廓上的所有点 + target_contour_points = [] + for contour in contours: + for point in contour: + # 检查点是否接近目标线(允许一定误差) + if abs(point[0][0] - target_line_x) < 5: # 5像素的误差范围 + target_contour_points.append((point[0][0], point[0][1])) + + # 如果找到了足够的点,计算斜率 + slope = 0 # 默认斜率为0(水平线) + if len(target_contour_points) >= 2: + # 使用线性回归计算斜率 + x_coords = np.array([p[0] for p in target_contour_points]) + y_coords = np.array([p[1] for p in target_contour_points]) + + if np.std(x_coords) > 0: # 避免除以零 + slope, _ = np.polyfit(x_coords, y_coords, 1) + + if observe: + result_img = img.copy() + # 标记底部最近的点 + cv2.circle(result_img, bottom_most_point, 10, (255, 255, 0), -1) + # 绘制三条竖线 + for x in vertical_lines: + cv2.line(result_img, (x, 0), (x, height), (0, 0, 255), 2) + # 显示目标线到中线的距离和斜率 + cv2.putText(result_img, f"Distance to center: {distance_to_center}px", (10, 30), + cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) + cv2.putText(result_img, f"Slope: {slope:.4f}", (10, 70), + cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) + cv2.imshow("目标线分析", result_img) + cv2.waitKey(delay) + + # 更新路径信息字典,包含新的目标线信息 + path_info = { + "target_line_x": target_line_x, + "distance_to_center": distance_to_center, + "target_line_slope": slope, + "bottom_most_point": bottom_most_point + } + + # 合并所有轮廓或选择最大的轮廓 + # 在这个场景中,我们可能有多个赛道段落,所以合并它们 + all_contours = np.vstack([contours[i] for i in range(len(contours))]) + all_contours = all_contours.reshape((-1, 1, 2)) + + # 使用多边形近似轮廓 + epsilon = 0.01 * cv2.arcLength(all_contours, False) + approx = cv2.approxPolyDP(all_contours, epsilon, False) + + if observe: + print(f"步骤6: 多边形近似,顶点数: {len(approx)}") + approx_img = img.copy() + cv2.drawContours(approx_img, [approx], -1, (255, 0, 0), 2) + cv2.imshow("多边形近似", approx_img) + cv2.waitKey(delay) + + # 获取图像尺寸 + height, width = img.shape[:2] + + # 提取赛道底部的点(靠近摄像机的点) + bottom_points = [p[0] for p in approx if p[0][1] > height * 0.7] + if not bottom_points: + if observe: + print("未找到靠近摄像机的赛道点") + return None, None + + # 计算底部点的平均x坐标,作为赛道中心线 + bottom_center_x = sum(p[0] for p in bottom_points) / len(bottom_points) + + # 提取赛道顶部的点(远离摄像机的点) + top_points = [p[0] for p in approx if p[0][1] < height * 0.3] + if not top_points: + if observe: + print("未找到远离摄像机的赛道点") + return None, None + + # 计算顶部点的平均x坐标 + top_center_x = sum(p[0] for p in top_points) / len(top_points) + + # 计算赛道方向(从底部到顶部的角度) + delta_x = top_center_x - bottom_center_x + track_angle = np.arctan2(height * 0.4, delta_x) * 180 / np.pi + + # 估算距离 + # 这里使用简化的方法:基于黄色区域在图像中的占比来估算距离 + yellow_area = cv2.countNonZero(mask) + total_area = height * width + area_ratio = yellow_area / total_area + + # 假设:区域比例与距离成反比(实际应用中需要标定) + # 这里使用一个简单的比例关系,实际应用需要根据相机参数和实际测量进行标定 + estimated_distance = 1.0 / (area_ratio + 0.01) # 避免除以零 + + # 将距离标准化到一个合理的范围(例如0-10米) + normalized_distance = min(10.0, max(0.0, estimated_distance / 100.0)) + + # 创建路径信息字典 + path_info = { + "bottom_center_x": bottom_center_x, + "top_center_x": top_center_x, + "track_angle": track_angle, + "area_ratio": area_ratio, + "is_straight": abs(track_angle) < 10, # 判断赛道是否笔直 + "turn_direction": "left" if delta_x < 0 else "right" if delta_x > 0 else "straight", + "target_line_x": target_line_x, + "distance_to_center": distance_to_center, + "target_line_slope": slope, + "bottom_most_point": bottom_most_point + } + + if observe: + print(f"步骤7: 路径分析 - 角度: {track_angle:.2f}°, 距离: {normalized_distance:.2f}m, 方向: {path_info['turn_direction']}") + result_img = img.copy() + # 绘制底部中心点 + cv2.circle(result_img, (int(bottom_center_x), int(height * 0.8)), 10, (0, 0, 255), -1) + # 绘制顶部中心点 + cv2.circle(result_img, (int(top_center_x), int(height * 0.2)), 10, (0, 0, 255), -1) + # 绘制路径线 + cv2.line(result_img, (int(bottom_center_x), int(height * 0.8)), + (int(top_center_x), int(height * 0.2)), (0, 255, 0), 2) + # 添加信息文本 + cv2.putText(result_img, f"Distance: {normalized_distance:.2f}m", (10, 30), + cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) + cv2.putText(result_img, f"Angle: {track_angle:.2f}°", (10, 70), + cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) + cv2.putText(result_img, f"Direction: {path_info['turn_direction']}", (10, 110), + cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) + + cv2.imshow("赛道分析结果", result_img) + cv2.waitKey(delay) + + return normalized_distance, path_info + +def visualize_track_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 + + # 获取图像尺寸 + height, width = img.shape[:2] + + # 创建输出图像 + output = img.copy() + + # 转换到HSV颜色空间 + hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) + + # 黄色的HSV范围 + lower_yellow = np.array([20, 100, 100]) + upper_yellow = np.array([30, 255, 255]) + + # 创建黄色的掩码 + mask = cv2.inRange(hsv, lower_yellow, upper_yellow) + + # 应用掩码,只保留黄色部分 + yellow_only = cv2.bitwise_and(img, img, mask=mask) + + # 进行边缘检测 + edges = preprocess_image(img) + + # 组合黄色掩码和边缘检测 + combined_mask = cv2.bitwise_and(mask, edges) if edges is not None else mask + + # 查找轮廓 + contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + + # 如果找到轮廓,绘制并分析 + if contours: + # 绘制所有轮廓 + cv2.drawContours(output, contours, -1, (0, 255, 0), 2) + + # 绘制三条竖线 + vertical_lines = [ + width // 2, # 中线 + int(width * 2 / 5), # 2/5位置的线 + int(width * 3 / 5) # 3/5位置的线 + ] + + # 找出与各条竖线的交点 + intersection_points = [] + + for x in vertical_lines: + # 绘制竖线 + cv2.line(output, (x, 0), (x, height), (0, 0, 255), 2) + + # 记录与当前竖线的交点 + line_intersections = [] + + # 对于每个轮廓 + for contour in contours: + # 遍历轮廓中的所有点对 + for j in range(len(contour) - 1): + pt1 = contour[j][0] + pt2 = contour[j+1][0] + + # 检查两点是否在竖线两侧 + if (pt1[0] <= x and pt2[0] >= x) or (pt1[0] >= x and pt2[0] <= x): + # 计算交点的y坐标 + if pt2[0] == pt1[0]: # 避免除以零 + y_intersect = pt1[1] + else: + slope = (pt2[1] - pt1[1]) / (pt2[0] - pt1[0]) + y_intersect = pt1[1] + slope * (x - pt1[0]) + + # 添加交点 + line_intersections.append((x, int(y_intersect))) + + # 在图像上标记交点 + for point in line_intersections: + cv2.circle(output, point, 5, (255, 0, 0), -1) + + # 寻找最底部的交点 + if line_intersections: + bottom_most = max(line_intersections, key=lambda p: p[1]) + intersection_points.append(bottom_most) + else: + intersection_points.append(None) + + # 找出底部最近的点 + valid_intersections = [p for p in intersection_points if p is not None] + + if valid_intersections: + bottom_most_point = max(valid_intersections, key=lambda p: p[1]) + bottom_most_index = intersection_points.index(bottom_most_point) + + # 计算目标线信息 + target_line_x = vertical_lines[bottom_most_index] + center_line_x = vertical_lines[0] + distance_to_center = target_line_x - center_line_x + + # 标记底部最近的点 + cv2.circle(output, bottom_most_point, 10, (255, 255, 0), -1) + + # 计算目标线的斜率 + target_contour_points = [] + for contour in contours: + for point in contour: + if abs(point[0][0] - target_line_x) < 5: + target_contour_points.append((point[0][0], point[0][1])) + + slope = 0 + if len(target_contour_points) >= 2: + x_coords = np.array([p[0] for p in target_contour_points]) + y_coords = np.array([p[1] for p in target_contour_points]) + + if np.std(x_coords) > 0: + slope, _ = np.polyfit(x_coords, y_coords, 1) + + # 在图像上添加目标线信息 + cv2.putText(output, f"Distance to center: {distance_to_center}px", (10, 150), + cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) + cv2.putText(output, f"Slope: {slope:.4f}", (10, 190), + cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) + + # 合并所有轮廓 + all_contours = np.vstack([contours[i] for i in range(len(contours))]) + all_contours = all_contours.reshape((-1, 1, 2)) + + # 使用多边形近似轮廓 + epsilon = 0.01 * cv2.arcLength(all_contours, False) + approx = cv2.approxPolyDP(all_contours, epsilon, False) + + # 提取赛道底部和顶部的点 + bottom_points = [p[0] for p in approx if p[0][1] > height * 0.7] + top_points = [p[0] for p in approx if p[0][1] < height * 0.3] + + # 如果找到了顶部和底部的点,计算中心线和方向 + if bottom_points and top_points: + bottom_center_x = sum(p[0] for p in bottom_points) / len(bottom_points) + top_center_x = sum(p[0] for p in top_points) / len(top_points) + + # 绘制底部和顶部中心点 + cv2.circle(output, (int(bottom_center_x), int(height * 0.8)), 10, (0, 0, 255), -1) + cv2.circle(output, (int(top_center_x), int(height * 0.2)), 10, (0, 0, 255), -1) + + # 绘制路径线 + cv2.line(output, (int(bottom_center_x), int(height * 0.8)), + (int(top_center_x), int(height * 0.2)), (0, 255, 0), 2) + + # 计算方向角度 + delta_x = top_center_x - bottom_center_x + track_angle = np.arctan2(height * 0.6, delta_x) * 180 / np.pi + + # 估算距离 + yellow_area = cv2.countNonZero(mask) + total_area = height * width + area_ratio = yellow_area / total_area + estimated_distance = 1.0 / (area_ratio + 0.01) + normalized_distance = min(10.0, max(0.0, estimated_distance / 100.0)) + + # 确定转向方向 + turn_direction = "left" if delta_x < 0 else "right" if delta_x > 0 else "straight" + + # 在图像上添加信息 + cv2.putText(output, f"Distance: {normalized_distance:.2f}m", (10, 30), + cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) + cv2.putText(output, f"Angle: {track_angle:.2f}°", (10, 70), + cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) + cv2.putText(output, f"Direction: {turn_direction}", (10, 110), + 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, yellow_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('Track Detection Process', resized) + cv2.waitKey(0) + cv2.destroyAllWindows() + +# 距离估算辅助函数 +def estimate_distance_to_track(image): + """ + 估算摄像机到赛道前方的距离 + + 参数: + image: 输入图像,可以是文件路径或者已加载的图像数组 + + 返回: + distance: 估算的距离(米) + path_angle: 赛道角度(度),正值表示向右转,负值表示向左转 + target_line_info: 目标线信息字典,包含到中线的距离和斜率 + """ + distance, path_info = detect_yellow_track(image, observe=False) + + if distance is None or path_info is None: + return None, None, None + + # 提取目标线信息 + target_line_info = { + "distance_to_center": path_info["distance_to_center"], + "slope": path_info["target_line_slope"] + } + + return distance, path_info["track_angle"], target_line_info + +def detect_horizontal_track_edge(image, observe=False, delay=1500): + """ + 检测正前方横向黄色赛道的边缘,并返回y值最大的边缘点 + + 参数: + image: 输入图像,可以是文件路径或者已加载的图像数组 + observe: 是否输出中间状态信息和可视化结果,默认为False + delay: 展示每个步骤的等待时间(毫秒),默认为1500ms + + 返回: + edge_point: 赛道前方边缘点的坐标 (x, y) + edge_info: 边缘信息字典 + """ + # 如果输入是字符串(文件路径),则加载图像 + if isinstance(image, str): + img = cv2.imread(image) + else: + img = image.copy() + + if img is None: + print("无法加载图像") + return None, None + + # 获取图像尺寸 + height, width = img.shape[:2] + + # 计算图像中间区域的范围(用于专注于正前方的赛道) + center_x = width // 2 + search_width = int(width * 2/3) # 搜索区域宽度为图像宽度的2/3 + search_height = height # 搜索区域高度为图像高度的1/1 + left_bound = center_x - search_width // 2 + right_bound = center_x + search_width // 2 + bottom_bound = height + top_bound = height - search_height + + if observe: + print("步骤1: 原始图像已加载") + search_region_img = img.copy() + # 绘制搜索区域 + cv2.rectangle(search_region_img, (left_bound, top_bound), (right_bound, bottom_bound), (255, 0, 0), 2) + cv2.line(search_region_img, (center_x, 0), (center_x, height), (0, 0, 255), 2) # 中线 + cv2.imshow("搜索区域", search_region_img) + cv2.waitKey(delay) + + # 转换到HSV颜色空间以便更容易提取黄色 + hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) + + # 黄色的HSV范围 + lower_yellow = np.array([20, 100, 100]) + upper_yellow = np.array([30, 255, 255]) + + # 创建黄色的掩码 + mask = cv2.inRange(hsv, lower_yellow, upper_yellow) + + if observe: + print("步骤2: 创建黄色掩码") + cv2.imshow("黄色掩码", mask) + cv2.waitKey(delay) + + # 使用形态学操作改善掩码质量 + kernel = np.ones((5, 5), np.uint8) + mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) # 闭操作填充小空洞 + mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) # 开操作移除小噪点 + + if observe: + print("步骤2.1: 形态学处理后的掩码") + cv2.imshow("处理后的掩码", mask) + cv2.waitKey(delay) + + # 应用掩码,只保留黄色部分 + yellow_only = cv2.bitwise_and(img, img, mask=mask) + + if observe: + print("步骤3: 提取黄色部分") + cv2.imshow("只保留黄色", yellow_only) + cv2.waitKey(delay) + + # 查找轮廓 + contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + + # 如果没有找到轮廓,返回None + if not contours: + if observe: + print("未找到轮廓") + return None, None + + if observe: + print(f"步骤4: 找到 {len(contours)} 个轮廓") + contour_img = img.copy() + cv2.drawContours(contour_img, contours, -1, (0, 255, 0), 2) + cv2.imshow("所有轮廓", contour_img) + cv2.waitKey(delay) + + # 筛选可能属于横向赛道的轮廓 + horizontal_contours = [] + + for contour in contours: + # 计算轮廓的边界框 + x, y, w, h = cv2.boundingRect(contour) + + # 计算轮廓的宽高比 + aspect_ratio = float(w) / max(h, 1) + + # 在搜索区域内且宽高比大于1(更宽而非更高)的轮廓更可能是横向线段 + if (left_bound <= x + w // 2 <= right_bound and + top_bound <= y + h // 2 <= bottom_bound and + aspect_ratio > 1.0): + horizontal_contours.append(contour) + + if not horizontal_contours: + if observe: + print("未找到符合条件的横向轮廓") + + # 如果没有找到符合条件的横向轮廓,尝试使用所有在搜索区域内的轮廓 + for contour in contours: + x, y, w, h = cv2.boundingRect(contour) + if (left_bound <= x + w // 2 <= right_bound and + top_bound <= y + h // 2 <= bottom_bound): + horizontal_contours.append(contour) + + if not horizontal_contours: + if observe: + print("在搜索区域内未找到任何轮廓") + return None, None + + if observe: + print(f"步骤4.1: 找到 {len(horizontal_contours)} 个可能的横向轮廓") + horizontal_img = img.copy() + cv2.drawContours(horizontal_img, horizontal_contours, -1, (0, 255, 0), 2) + cv2.imshow("横向轮廓", horizontal_img) + cv2.waitKey(delay) + + # 收集所有可能的横向轮廓点 + all_horizontal_points = [] + for contour in horizontal_contours: + for point in contour: + x, y = point[0] + if (left_bound <= x <= right_bound and + top_bound <= y <= bottom_bound): + all_horizontal_points.append((x, y)) + + if not all_horizontal_points: + if observe: + print("在搜索区域内未找到有效点") + return None, None + + # 按y值对点进行分组(针对不同的水平线段) + # 使用聚类方法将点按y值分组 + y_values = np.array([p[1] for p in all_horizontal_points]) + y_values = y_values.reshape(-1, 1) # 转换为列向量 + + # 如果点较少,直接按y值简单分组 + if len(y_values) < 10: + # 简单分组:通过y值差异判断是否属于同一水平线 + y_groups = [] + current_group = [all_horizontal_points[0]] + current_y = all_horizontal_points[0][1] + + for i in range(1, len(all_horizontal_points)): + point = all_horizontal_points[i] + if abs(point[1] - current_y) < 10: # 如果y值接近当前组的y值 + current_group.append(point) + else: + y_groups.append(current_group) + current_group = [point] + current_y = point[1] + + if current_group: + y_groups.append(current_group) + else: + # 使用K-means聚类按y值将点分为不同组 + max_clusters = min(5, len(y_values) // 2) # 最多5个聚类或点数的一半 + + # 尝试不同数量的聚类,找到最佳分组 + best_score = -1 + best_labels = None + + for n_clusters in range(1, max_clusters + 1): + kmeans = KMeans(n_clusters=n_clusters, n_init=10, random_state=0).fit(y_values) + score = silhouette_score(y_values, kmeans.labels_) if n_clusters > 1 else 0 + + if score > best_score: + best_score = score + best_labels = kmeans.labels_ + + # 根据聚类结果分组 + y_groups = [[] for _ in range(max(best_labels) + 1)] + for i, point in enumerate(all_horizontal_points): + group_idx = best_labels[i] + y_groups[group_idx].append(point) + + if observe: + clusters_img = img.copy() + colors = [(0, 0, 255), (0, 255, 0), (255, 0, 0), (255, 255, 0), (0, 255, 255)] + + for i, group in enumerate(y_groups): + color = colors[i % len(colors)] + for point in group: + cv2.circle(clusters_img, point, 3, color, -1) + + cv2.imshow("按Y值分组的点", clusters_img) + cv2.waitKey(delay) + + # 为每个组计算平均y值 + avg_y_values = [] + for group in y_groups: + avg_y = sum(p[1] for p in group) / len(group) + avg_y_values.append((avg_y, group)) + + # 按平均y值降序排序(越大的y值越靠近底部,也就是越靠近相机) + avg_y_values.sort(reverse=True) + + # 从y值最大的组开始分析,找到符合横向赛道特征的组 + selected_group = None + selected_slope = 0 + + for avg_y, group in avg_y_values: + # 计算该组点的斜率 + if len(group) < 2: + continue + + x_coords = np.array([p[0] for p in group]) + y_coords = np.array([p[1] for p in group]) + + if np.std(x_coords) <= 0: + continue + + slope, _ = np.polyfit(x_coords, y_coords, 1) + + # 判断该组是否可能是横向赛道 + # 横向赛道的斜率应该比较小(接近水平) + if abs(slope) < 0.5: # 允许一定的倾斜 + selected_group = group + selected_slope = slope + break + + # 如果没有找到符合条件的组,使用y值最大的组 + if selected_group is None and avg_y_values: + selected_group = avg_y_values[0][1] + + # 重新计算斜率 + if len(selected_group) >= 2: + x_coords = np.array([p[0] for p in selected_group]) + y_coords = np.array([p[1] for p in selected_group]) + + if np.std(x_coords) > 0: + selected_slope, _ = np.polyfit(x_coords, y_coords, 1) + + if selected_group is None: + if observe: + print("未能找到有效的横向赛道线") + return None, None + + # 找出选定组中y值最大的点(最靠近相机的点) + bottom_edge_point = max(selected_group, key=lambda p: p[1]) + + if observe: + print(f"步骤5: 找到边缘点 {bottom_edge_point}") + edge_img = img.copy() + # 绘制选定的组 + for point in selected_group: + cv2.circle(edge_img, point, 3, (255, 0, 0), -1) + # 标记边缘点 + cv2.circle(edge_img, bottom_edge_point, 10, (0, 0, 255), -1) + cv2.imshow("选定的横向线和边缘点", edge_img) + cv2.waitKey(delay) + + # 计算这个点到中线的距离 + distance_to_center = bottom_edge_point[0] - center_x + + # 使用选定组的斜率 + slope = selected_slope + + # 计算中线与检测到的横向线的交点 + # 横向线方程: y = slope * (x - edge_x) + edge_y + # 中线方程: x = center_x + # 解这个方程组得到交点坐标 + edge_x, edge_y = bottom_edge_point + intersection_x = center_x + intersection_y = slope * (center_x - edge_x) + edge_y + intersection_point = (int(intersection_x), int(intersection_y)) + + # 计算交点到图像底部的距离(以像素为单位) + distance_to_bottom = height - intersection_y + + if observe: + slope_img = img.copy() + # 画出底部边缘点 + cv2.circle(slope_img, bottom_edge_point, 10, (0, 0, 255), -1) + # 画出选定组中的所有点 + for point in selected_group: + cv2.circle(slope_img, point, 3, (255, 0, 0), -1) + # 使用斜率画一条线来表示边缘方向 + line_length = 200 + end_x = bottom_edge_point[0] + line_length + end_y = int(bottom_edge_point[1] + slope * line_length) + start_x = bottom_edge_point[0] - line_length + start_y = int(bottom_edge_point[1] - slope * line_length) + cv2.line(slope_img, (start_x, start_y), (end_x, end_y), (0, 255, 0), 2) + + # 画出中线 + cv2.line(slope_img, (center_x, 0), (center_x, height), (0, 0, 255), 2) + + # 标记中线与横向线的交点 (高亮显示) + cv2.circle(slope_img, intersection_point, 12, (255, 0, 255), -1) + cv2.circle(slope_img, intersection_point, 5, (255, 255, 255), -1) + + # 画出交点到底部的距离线 + cv2.line(slope_img, intersection_point, (intersection_x, height), (255, 255, 0), 2) + + cv2.putText(slope_img, f"Slope: {slope:.4f}", (10, 30), + cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) + cv2.putText(slope_img, f"Distance to center: {distance_to_center}px", (10, 70), + cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) + cv2.putText(slope_img, f"Distance to bottom: {distance_to_bottom:.1f}px", (10, 110), + cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) + cv2.putText(slope_img, f"中线交点: ({intersection_point[0]}, {intersection_point[1]})", (10, 150), + cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) + cv2.imshow("边缘斜率和中线交点", slope_img) + cv2.waitKey(delay) + + # 创建边缘信息字典 + edge_info = { + "x": bottom_edge_point[0], + "y": bottom_edge_point[1], + "distance_to_center": distance_to_center, + "slope": slope, + "is_horizontal": abs(slope) < 0.1, # 判断边缘是否接近水平 + "points_count": len(selected_group), # 该组中点的数量 + "intersection_point": intersection_point, # 中线与横向线的交点 + "distance_to_bottom": distance_to_bottom, # 交点到图像底部的距离 + "points": selected_group # 添加选定的点组 + } + + return bottom_edge_point, edge_info + +def visualize_horizontal_track_edge(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 + + # 获取图像尺寸 + height, width = img.shape[:2] + + # 创建输出图像 + output = img.copy() + + # 执行横向赛道检测 + edge_point, edge_info = detect_horizontal_track_edge(img, observe=False) + + # 如果成功检测到边缘 + if edge_point is not None and edge_info is not None: + # 获取信息 + center_x = width // 2 + + # 绘制中线 + cv2.line(output, (center_x, 0), (center_x, height), (0, 0, 255), 2) + + # 获取关键信息 + selected_slope = edge_info['slope'] + distance_to_center = edge_info['distance_to_center'] + intersection_point = edge_info['intersection_point'] + distance_to_bottom = edge_info['distance_to_bottom'] + is_horizontal = edge_info['is_horizontal'] + + # 绘制检测到的点,兼容旧版本的edge_info字典 + if 'points' in edge_info and edge_info['points']: + for point in edge_info['points']: + cv2.circle(output, point, 3, (0, 255, 0), -1) + + # 标记边缘点 + cv2.circle(output, edge_point, 10, (0, 0, 255), -1) + + # 使用斜率画一条线来表示边缘方向 + line_length = 200 + end_x = edge_point[0] + line_length + end_y = int(edge_point[1] + selected_slope * line_length) + start_x = edge_point[0] - line_length + start_y = int(edge_point[1] - selected_slope * line_length) + cv2.line(output, (start_x, start_y), (end_x, end_y), (0, 255, 0), 2) + + # 标记中线与横向线的交点 (高亮显示) + cv2.circle(output, intersection_point, 12, (255, 0, 255), -1) + cv2.circle(output, intersection_point, 5, (255, 255, 255), -1) + + # 画出交点到底部的距离线 + cv2.line(output, intersection_point, (intersection_point[0], height), (255, 255, 0), 2) + + # 添加信息文本 + cv2.putText(output, f"边缘点: ({edge_point[0]}, {edge_point[1]})", (10, 30), + cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) + cv2.putText(output, f"到中线距离: {distance_to_center}像素", (10, 60), + cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) + cv2.putText(output, f"斜率: {selected_slope:.4f}", (10, 90), + cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) + cv2.putText(output, f"是否水平: {is_horizontal}", (10, 120), + cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) + cv2.putText(output, f"点数量: {edge_info['points_count']}", (10, 150), + cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) + cv2.putText(output, f"到底部距离: {distance_to_bottom:.1f}像素", (10, 180), + cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) + cv2.putText(output, f"中线交点: ({intersection_point[0]}, {intersection_point[1]})", (10, 210), + cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) + else: + # 如果没有检测到,添加提示 + cv2.putText(output, "未检测到横向赛道", (width//4, height//2), + cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 0, 255), 2) + + # 如果提供了保存路径,保存结果图像 + if save_path: + cv2.imwrite(save_path, output) + if observe: + print(f"结果已保存到: {save_path}") + + # 显示结果 + if observe: + # 转换到HSV颜色空间 + hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) + + # 黄色的HSV范围 + lower_yellow = np.array([20, 100, 100]) + upper_yellow = np.array([30, 255, 255]) + + # 创建黄色的掩码 + mask = cv2.inRange(hsv, lower_yellow, upper_yellow) + + # 应用形态学操作 + kernel = np.ones((5, 5), np.uint8) + mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) + mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) + + # 应用掩码,只保留黄色部分 + yellow_only = cv2.bitwise_and(img, img, mask=mask) + + # 创建包含所有处理步骤的结果图像 + result = np.hstack((img, yellow_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('横向赛道边缘检测', resized) + cv2.waitKey(delay) + if save_path is None: # 如果没有保存,则等待按键 + cv2.waitKey(0) + cv2.destroyAllWindows() + + return output + +# 用法示例 +if __name__ == "__main__": + # 替换为实际图像路径 + image_path = "path/to/track/image.png" + + # 检测赛道并估算距离 + distance, path_info = detect_yellow_track(image_path, observe=True, delay=1500) + if distance is not None: + print(f"估算距离: {distance:.2f}米") + print(f"赛道角度: {path_info['track_angle']:.2f}°") + print(f"转向方向: {path_info['turn_direction']}") + print(f"目标线到中线距离: {path_info['distance_to_center']}像素") + print(f"目标线斜率: {path_info['target_line_slope']:.4f}") + + # 可视化检测过程 + visualize_track_detection(image_path, observe=True, delay=1500) + + # 检测横向赛道边缘 + edge_point, edge_info = detect_horizontal_track_edge(image_path, observe=True, delay=1500) + if edge_point is not None: + print(f"边缘点坐标: ({edge_point[0]}, {edge_point[1]})") + print(f"到中线距离: {edge_info['distance_to_center']}像素") + print(f"边缘斜率: {edge_info['slope']:.4f}") + print(f"是否水平: {edge_info['is_horizontal']}") + + # 可视化横向赛道边缘检测 + visualize_horizontal_track_edge(image_path, observe=True, delay=1500) + + # 其他检测和可视化... \ No newline at end of file