更新黄色赛道检测演示程序的输入输出路径,替换边缘检测函数为新版本以提高检测准确性,并优化线段合并和选择逻辑,增强了对中上部线段的偏好。

This commit is contained in:
Havoc 2025-05-24 22:34:27 +08:00
parent 924a8e07c6
commit 1c3c07d68a
4 changed files with 188 additions and 45 deletions

BIN
res/path/test-v2/2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

BIN
res/path/test-v2/3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

View File

@ -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='显示处理步骤')

View File

@ -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]
@ -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,8 +798,12 @@ 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]):
# 如果线在图像更上方,只要不太高也可以接受
if bottom_edge_point[1] < valid_y_range[0] and bottom_edge_point[1] > height * 0.2:
pass # 接受较高的线
else:
valid_result = False
reason += "边缘点y坐标超出有效范围; "
@ -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 # 添加选定的点组
}