diff --git a/res/path/test-v2/2.jpg b/res/path/test-v2/2.jpg new file mode 100644 index 0000000..58e0d62 Binary files /dev/null and b/res/path/test-v2/2.jpg differ diff --git a/res/path/test-v2/3.jpg b/res/path/test-v2/3.jpg new file mode 100644 index 0000000..78a6925 Binary files /dev/null and b/res/path/test-v2/3.jpg differ diff --git a/test/task-path-track/yellow_track_demo.py b/test/task-path-track/yellow_track_demo.py index 88cf740..8b5833c 100644 --- a/test/task-path-track/yellow_track_demo.py +++ b/test/task-path-track/yellow_track_demo.py @@ -17,7 +17,7 @@ def process_image(image_path, save_dir=None, show_steps=False): # 检测赛道并估算距离 start_time = time.time() - edge_point, edge_info = detect_horizontal_track_edge(image_path, observe=show_steps, save_log=True, delay=800) + edge_point, edge_info = detect_horizontal_track_edge_v2(image_path, observe=show_steps, save_log=True, delay=800) processing_time = time.time() - start_time # 输出结果 @@ -44,8 +44,8 @@ def process_image(image_path, save_dir=None, show_steps=False): 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/image_20250513_162556.png', help='输出结果的保存路径') + parser.add_argument('--input', type=str, default='res/path/test-1.jpg', help='输入图像或视频的路径') + parser.add_argument('--output', type=str, default='res/path/test-v2/2-end.jpg', help='输出结果的保存路径') parser.add_argument('--type', type=str, choices=['image', 'video'], help='输入类型,不指定会自动检测') parser.add_argument('--show', default=True, action='store_true', help='显示处理步骤') diff --git a/utils/detect_track.py b/utils/detect_track.py index 41e57fb..f5e4b4f 100644 --- a/utils/detect_track.py +++ b/utils/detect_track.py @@ -19,7 +19,7 @@ def detect_horizontal_track_edge(image, observe=False, delay=1000, save_log=True edge_point: 赛道前方边缘点的坐标 (x, y) edge_info: 边缘信息字典 """ - observe = False # TEST + # observe = False # TEST # 如果输入是字符串(文件路径),则加载图像 if isinstance(image, str): img = cv2.imread(image) @@ -350,8 +350,7 @@ def detect_horizontal_track_edge(image, observe=False, delay=1000, save_log=True def detect_horizontal_track_edge_v2(image, observe=False, delay=1000, save_log=True): """ 检测正前方横向黄色赛道的边缘,并返回y值最大的边缘点 - 优先检测下方横线,但在遇到下方线截断的情况时会考虑上边缘 - 但容易识别到上面的线。 + 优先检测中部和上部的横线,特别是对于远处的横线 参数: image: 输入图像,可以是文件路径或者已加载的图像数组 @@ -362,7 +361,7 @@ def detect_horizontal_track_edge_v2(image, observe=False, delay=1000, save_log=T edge_point: 赛道前方边缘点的坐标 (x, y) edge_info: 边缘信息字典 """ - observe = False # TEST + # observe = False # TEST # 如果输入是字符串(文件路径),则加载图像 if isinstance(image, str): img = cv2.imread(image) @@ -385,9 +384,9 @@ def detect_horizontal_track_edge_v2(image, observe=False, delay=1000, save_log=T bottom_bound = height top_bound = height - search_height - # 定义合理的值范围 - valid_y_range = (height * 0.5, height) # 有效的y坐标范围(下半部分图像) - max_slope = 0.15 # 最大允许斜率(接近水平) + # 定义合理的值范围 - 修改为更关注中上部区域 + valid_y_range = (height * 0.1, height * 0.6) # 有效的y坐标范围(中上部分图像),扩大范围到90% + max_slope = 0.2 # 最大允许斜率(接近水平) min_line_length = width * 0.2 # 最小线长度 if observe: @@ -467,7 +466,7 @@ def detect_horizontal_track_edge_v2(image, observe=False, delay=1000, save_log=T cv2.waitKey(delay) # 使用霍夫变换检测直线,降低阈值以检测更多线段 - lines = cv2.HoughLinesP(edges, 1, np.pi/180, threshold=25, + lines = cv2.HoughLinesP(edges, 1, np.pi/180, threshold=30, minLineLength=width*0.1, maxLineGap=30) if lines is None or len(lines) == 0: @@ -477,21 +476,147 @@ def detect_horizontal_track_edge_v2(image, observe=False, delay=1000, save_log=T if observe: debug(f"步骤5: 检测到 {len(lines)} 条直线", "处理") + print(f"len(lines): {len(lines)}") lines_img = img.copy() - for line in lines: + for i, line in enumerate(lines): x1, y1, x2, y2 = line[0] - cv2.line(lines_img, (x1, y1), (x2, y2), (0, 255, 0), 2) + # 根据线段长度使用不同颜色 + line_length = np.sqrt((x2-x1)**2 + (y2-y1)**2) + # 使用HSV颜色空间生成不同的颜色 + hue = (i * 30) % 180 # 每30度一个颜色 + color = cv2.cvtColor(np.uint8([[[hue, 255, 255]]]), cv2.COLOR_HSV2BGR)[0][0] + color = (int(color[0]), int(color[1]), int(color[2])) + cv2.line(lines_img, (x1, y1), (x2, y2), color, 2) + cv2.putText(lines_img, f"{i}", ((x1+x2)//2, (y1+y2)//2), + cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2) cv2.imshow("检测到的直线", lines_img) cv2.waitKey(delay) + # 过滤和合并相似的线段 + filtered_lines = [] + for line in lines: + x1, y1, x2, y2 = line[0] + # 确保x1 < x2 + if x1 > x2: + x1, x2 = x2, x1 + y1, y2 = y2, y1 + filtered_lines.append([x1, y1, x2, y2]) + + # 合并相似线段 + merged_lines = [] + used_indices = set() + + for i, line1 in enumerate(filtered_lines): + if i in used_indices: + continue + + x1, y1, x2, y2 = line1 + similar_lines = [line1] + used_indices.add(i) + + # 查找与当前线段相似的其他线段 + for j, line2 in enumerate(filtered_lines): + if j in used_indices or i == j: + continue + + x3, y3, x4, y4 = line2 + + # 计算两条线段的斜率 + slope1 = (y2 - y1) / (x2 - x1) if abs(x2 - x1) > 5 else 100 + slope2 = (y4 - y3) / (x4 - x3) if abs(x4 - x3) > 5 else 100 + + # 计算两条线段的中点 + mid1_x, mid1_y = (x1 + x2) / 2, (y1 + y2) / 2 + mid2_x, mid2_y = (x3 + x4) / 2, (y3 + y4) / 2 + + # 计算中点之间的距离 + mid_dist = np.sqrt((mid2_x - mid1_x)**2 + (mid2_y - mid1_y)**2) + + # 计算线段端点之间的最小距离 + end_dists = [ + np.sqrt((x1-x3)**2 + (y1-y3)**2), + np.sqrt((x1-x4)**2 + (y1-y4)**2), + np.sqrt((x2-x3)**2 + (y2-y3)**2), + np.sqrt((x2-x4)**2 + (y2-y4)**2) + ] + min_end_dist = min(end_dists) + + # 判断两条线段是否相似:满足以下条件之一 + # 1. 斜率接近且中点距离不太远 + # 2. 斜率接近且端点之间距离很近(可能是连接的线段) + # 3. 端点非常接近(几乎连接),且斜率差异不太大 + if (abs(slope1 - slope2) < 0.15 and mid_dist < height * 0.15) or \ + (abs(slope1 - slope2) < 0.1 and min_end_dist < height * 0.05) or \ + (min_end_dist < height * 0.03 and abs(slope1 - slope2) < 0.25): + similar_lines.append(line2) + used_indices.add(j) + + # 如果找到相似线段,合并它们 + if len(similar_lines) > 1: + # 合并所有相似线段的端点 + all_points = [] + for line in similar_lines: + all_points.append((line[0], line[1])) # 起点 + all_points.append((line[2], line[3])) # 终点 + + # 找出x坐标的最小值和最大值 + min_x = min(p[0] for p in all_points) + max_x = max(p[0] for p in all_points) + + # 使用所有点拟合一条直线 + x_points = np.array([p[0] for p in all_points]).reshape(-1, 1) + y_points = np.array([p[1] for p in all_points]) + + # 使用RANSAC拟合更稳定的直线 + ransac = linear_model.RANSACRegressor(residual_threshold=5.0) + ransac.fit(x_points, y_points) + + # 获取拟合的斜率和截距 + merged_slope = ransac.estimator_.coef_[0] + merged_intercept = ransac.estimator_.intercept_ + + # 计算新的端点 + y_min = int(merged_slope * min_x + merged_intercept) + y_max = int(merged_slope * max_x + merged_intercept) + + # 添加合并后的线段 + merged_lines.append([min_x, y_min, max_x, y_max]) + else: + # 如果没有相似线段,直接添加原线段 + merged_lines.append(line1) + + # 将合并后的线段转换为霍夫变换的格式 + merged_hough_lines = [] + for line in merged_lines: + merged_hough_lines.append(np.array([[line[0], line[1], line[2], line[3]]])) + + if observe: + debug(f"步骤5.1: 合并后剩余 {len(merged_hough_lines)} 条线", "处理") + merged_img = img.copy() + for i, line in enumerate(merged_hough_lines): + x1, y1, x2, y2 = line[0] + # 使用HSV颜色空间生成不同的颜色 + hue = (i * 50) % 180 # 每50度一个颜色 + color = cv2.cvtColor(np.uint8([[[hue, 255, 255]]]), cv2.COLOR_HSV2BGR)[0][0] + color = (int(color[0]), int(color[1]), int(color[2])) + cv2.line(merged_img, (x1, y1), (x2, y2), color, 3) + # 显示线段编号 + cv2.putText(merged_img, f"{i}", ((x1+x2)//2, (y1+y2)//2), + cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2) + cv2.imshow("合并后的线段", merged_img) + cv2.waitKey(delay) + + # 使用合并后的线段继续处理 + lines = merged_hough_lines + # 筛选水平线,但放宽斜率条件 horizontal_lines = [] # 分别存储上方和下方的水平线 lower_horizontal_lines = [] upper_horizontal_lines = [] - # 定义上下分界线位置 (以图像中部再下移一点作为分界) - lower_upper_boundary = height * 0.65 + # 定义上下分界线位置 - 修改为图像的60%处,使上方区域更大 + lower_upper_boundary = height * 0.6 for line in lines: x1, y1, x2, y2 = line[0] @@ -501,7 +626,7 @@ def detect_horizontal_track_edge_v2(image, observe=False, delay=1000, save_log=T continue slope = (y2 - y1) / (x2 - x1) - + # 筛选接近水平的线 (斜率接近0),但容许更大的倾斜度 if abs(slope) < max_slope: # 确保线在搜索区域内 @@ -511,10 +636,12 @@ def detect_horizontal_track_edge_v2(image, observe=False, delay=1000, save_log=T mid_y = (y1 + y2) / 2 line_length = np.sqrt((x2-x1)**2 + (y2-y1)**2) - # 过滤掉短线段和太靠近图像上部的线 - if line_length >= min_line_length and mid_y >= valid_y_range[0]: - # 计算线段在图像中的位置得分(越靠近底部得分越高) - position_score = min(1.0, (mid_y - valid_y_range[0]) / (valid_y_range[1] - valid_y_range[0])) + # 过滤掉短线段 + if line_length >= min_line_length: + # 计算线段在图像中的位置得分 + # 修改:更偏好中部和上部的线段,使用高斯函数来优化位置评分 + optimal_y = height * 0.45 # 最佳高度在图像45%处 + position_score = np.exp(-0.5 * ((mid_y - optimal_y) / (height * 0.2))**2) # 计算长度得分(越长越好) length_score = min(1.0, line_length / (width * 0.5)) @@ -526,8 +653,8 @@ def detect_horizontal_track_edge_v2(image, observe=False, delay=1000, save_log=T mid_x = (x1 + x2) / 2 center_score = max(0.0, 1.0 - abs(mid_x - center_x) / (width * 0.3)) - # 计算综合得分,增加位置得分的权重,更强调下方线 - quality_score = position_score * 0.6 + length_score * 0.2 + slope_score * 0.15 + center_score * 0.05 + # 计算综合得分,增加位置得分的权重,强调中上部位置 + quality_score = position_score * 0.5 + length_score * 0.15 + slope_score * 0.25 + center_score * 0.1 # 保存线段、其y坐标、斜率、长度和质量得分 line_info = (line[0], mid_y, slope, line_length, quality_score) @@ -541,6 +668,9 @@ def detect_horizontal_track_edge_v2(image, observe=False, delay=1000, save_log=T # 同时保存到总列表中 horizontal_lines.append(line_info) + if observe: + print(f"horizontal_lines: {horizontal_lines}") + if not horizontal_lines: if observe: error("未检测到合格的水平线", "失败") @@ -570,19 +700,19 @@ def detect_horizontal_track_edge_v2(image, observe=False, delay=1000, save_log=T cv2.imshow("水平线", h_lines_img) cv2.waitKey(delay) - # 优先选择下方的线,如果没有下方的线才考虑上方的线 - if lower_horizontal_lines: - selected_line = lower_horizontal_lines[0][0] - selected_slope = lower_horizontal_lines[0][2] - selected_score = lower_horizontal_lines[0][4] - if observe: - debug("选择下方水平线", "处理") - elif upper_horizontal_lines: + # 修改:优先选择上方的线,如果没有上方的线才考虑下方的线 + if upper_horizontal_lines: selected_line = upper_horizontal_lines[0][0] selected_slope = upper_horizontal_lines[0][2] selected_score = upper_horizontal_lines[0][4] if observe: - debug("没有合适的下方线,选择上方水平线", "处理") + debug("选择上方水平线", "处理") + elif lower_horizontal_lines: + selected_line = lower_horizontal_lines[0][0] + selected_slope = lower_horizontal_lines[0][2] + selected_score = lower_horizontal_lines[0][4] + if observe: + debug("没有合适的上方线,选择下方水平线", "处理") else: # 理论上不会进入这个分支,因为前面已经检查过horizontal_lines非空 if observe: @@ -604,17 +734,17 @@ def detect_horizontal_track_edge_v2(image, observe=False, delay=1000, save_log=T bottom_edge_point = (x2, y2) # 如果得分过低,可能是错误识别,尝试使用边缘点拟合 - if selected_score < 0.4 and len(bottom_points) >= 5: + if selected_score < 0.4 and len(top_points) >= 5: # 修改:优先使用顶部点进行拟合 if observe: debug(f"线段质量得分过低: {selected_score:.2f},尝试使用边缘点拟合", "处理") - # 筛选下半部分的点 - valid_bottom_points = [p for p in bottom_points if p[1] >= valid_y_range[0]] + # 筛选中上部分的点 + valid_points = [p for p in top_points if valid_y_range[0] <= p[1] <= valid_y_range[1]] - if len(valid_bottom_points) >= 5: + if len(valid_points) >= 5: # 使用RANSAC拟合直线以去除异常值 - x_points = np.array([p[0] for p in valid_bottom_points]).reshape(-1, 1) - y_points = np.array([p[1] for p in valid_bottom_points]) + x_points = np.array([p[0] for p in valid_points]).reshape(-1, 1) + y_points = np.array([p[1] for p in valid_points]) ransac = linear_model.RANSACRegressor(residual_threshold=5.0) ransac.fit(x_points, y_points) @@ -650,7 +780,7 @@ def detect_horizontal_track_edge_v2(image, observe=False, delay=1000, save_log=T debug(f"使用拟合直线,斜率: {fitted_slope:.4f}, 内点比例: {inlier_ratio:.2f}", "处理") fitted_line_img = img.copy() cv2.line(fitted_line_img, (x1, y1), (x2, y2), (0, 255, 255), 2) - for i, point in enumerate(valid_bottom_points): + for i, point in enumerate(valid_points): color = (0, 255, 0) if inlier_mask[i] else (0, 0, 255) cv2.circle(fitted_line_img, point, 3, color, -1) cv2.imshow("拟合线和内点", fitted_line_img) @@ -668,10 +798,14 @@ def detect_horizontal_track_edge_v2(image, observe=False, delay=1000, save_log=T valid_result = True reason = "" - # 检查边缘点是否在有效范围内 + # 修改:调整有效范围检查,适应中上部分的线 if not (valid_y_range[0] <= bottom_edge_point[1] <= valid_y_range[1]): - valid_result = False - reason += "边缘点y坐标超出有效范围; " + # 如果线在图像更上方,只要不太高也可以接受 + if bottom_edge_point[1] < valid_y_range[0] and bottom_edge_point[1] > height * 0.2: + pass # 接受较高的线 + else: + valid_result = False + reason += "边缘点y坐标超出有效范围; " # 检查线段长度 line_length = np.sqrt((x2-x1)**2 + (y2-y1)**2) @@ -700,8 +834,8 @@ def detect_horizontal_track_edge_v2(image, observe=False, delay=1000, save_log=T intersection_y = selected_slope * (center_x - x1) + y1 intersection_point = (int(intersection_x), int(intersection_y)) - # 检查交点的y坐标是否在有效范围内 - if not (valid_y_range[0] <= intersection_y <= valid_y_range[1]): + # 修改:放宽交点y坐标的有效范围检查 + if intersection_y < height * 0.2 or intersection_y > height * 0.95: valid_result = False reason += "交点y坐标超出有效范围; " @@ -729,6 +863,9 @@ def detect_horizontal_track_edge_v2(image, observe=False, delay=1000, save_log=T cv2.line(slope_img, intersection_point, (intersection_x, height), (255, 255, 0), 2) # 画出上下分界线 cv2.line(slope_img, (0, int(lower_upper_boundary)), (width, int(lower_upper_boundary)), (255, 0, 255), 1) + # 画出有效高度范围 + cv2.line(slope_img, (0, int(valid_y_range[0])), (width, int(valid_y_range[0])), (255, 255, 0), 1) + cv2.line(slope_img, (0, int(valid_y_range[1])), (width, int(valid_y_range[1])), (255, 255, 0), 1) cv2.putText(slope_img, f"Slope: {selected_slope:.4f}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, line_color, 2) @@ -778,10 +915,16 @@ def detect_horizontal_track_edge_v2(image, observe=False, delay=1000, save_log=T "score": selected_score, "valid": valid_result, "reason": reason if not valid_result else "", - "is_lower_line": len(lower_horizontal_lines) > 0 and selected_line == lower_horizontal_lines[0][0] + "is_upper_line": len(upper_horizontal_lines) > 0 and selected_line == upper_horizontal_lines[0][0] } info(f"横向边缘检测结果: {log_info}", "日志") + # 如果结果无效,但检测到了一些线,仍然返回结果,不拒绝太靠近底部的线 + if not valid_result and "边缘点y坐标超出有效范围" in reason and bottom_edge_point[1] > height * 0.8: + # 仍然接受靠近底部的线 + valid_result = True + reason = "" + # 如果结果无效,可能需要返回失败 if not valid_result: return None, None @@ -797,7 +940,7 @@ def detect_horizontal_track_edge_v2(image, observe=False, delay=1000, save_log=T "intersection_point": intersection_point, # 中线与横向线的交点 "distance_to_bottom": distance_to_bottom, # 交点到图像底部的距离 "score": selected_score, # 线段质量得分 - "is_lower_line": len(lower_horizontal_lines) > 0 and selected_line == lower_horizontal_lines[0][0] # 是否为下方线 + "is_upper_line": len(upper_horizontal_lines) > 0 and selected_line == upper_horizontal_lines[0][0] # 是否为上方线 # "points": selected_points # 添加选定的点组 }