mi-task/utils/detect_dual_track_lines.py

850 lines
38 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

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

import cv2
import numpy as np
import os
import datetime
from utils.base_line_handler import _merge_collinear_lines_iterative
from utils.log_helper import LogHelper, get_logger, section, info, debug, warning, error, success, timing
def detect_dual_track_lines(image, observe=False, delay=1000, save_log=True,
min_slope_threshold=0.4,
min_line_length=0.05,
max_line_gap=40):
"""
检测左右两条平行的黄色轨道线,优化后能够更准确处理各种路况
参数:
image: 输入图像,可以是文件路径或者已加载的图像数组
observe: 是否输出中间状态信息和可视化结果默认为False
delay: 展示每个步骤的等待时间(毫秒)
save_log: 是否保存日志和图像
min_slope_threshold: 最小斜率阈值
min_line_length: 最小线段长度
max_line_gap: 最大线段间距
返回:
tuple: (中心线信息, 左轨迹线信息, 右轨迹线信息)
如果检测失败返回(None, None, None)
改进:
- 使用多点采样+多项式拟合计算更准确的中心线
- 优化对左右轨道线的选择,考虑平行性和合理宽度
- 增强对倾斜轨道和石板路的处理能力
"""
# 如果输入是字符串(文件路径),则加载图像
if isinstance(image, str):
img = cv2.imread(image)
else:
img = image.copy()
if img is None:
error("无法加载图像", "失败")
return None, None, None
# 获取图像尺寸
height, width = img.shape[:2]
# 计算图像中间区域的范围
center_x = width // 2
# 转换到HSV颜色空间以便更容易提取黄色
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# 标准黄色的HSV范围
lower_yellow = np.array([15, 80, 80])
upper_yellow = np.array([35, 255, 255])
# 创建黄色的掩码
mask = cv2.inRange(hsv, lower_yellow, upper_yellow)
kernel = np.ones((5, 5), np.uint8)
mask = cv2.dilate(mask, kernel, iterations=1)
mask = cv2.erode(mask, np.ones((3, 3), np.uint8), iterations=1)
if observe:
debug("步骤1: 创建黄色掩码", "处理")
mask_display = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)
cv2.putText(mask_display, "Step 1: Yellow Mask", (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
cv2.putText(mask_display, f"Lower: {lower_yellow}", (10, 40), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 255, 0), 1)
cv2.putText(mask_display, f"Upper: {upper_yellow}", (10, 55), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 255, 0), 1)
cv2.imshow("黄色掩码", mask_display)
cv2.waitKey(delay)
# 裁剪底部区域重点关注近处的黄线
bottom_roi_height = int(height * 0.4) # 增加关注区域到图像底部60%
bottom_roi = mask[height-bottom_roi_height:, :]
if observe:
debug("步骤1.5: 底部区域掩码", "处理")
bottom_roi_display = cv2.cvtColor(bottom_roi, cv2.COLOR_GRAY2BGR)
cv2.putText(bottom_roi_display, "Step 1.5: Bottom ROI", (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
cv2.putText(bottom_roi_display, f"ROI Height: {bottom_roi_height}px ({bottom_roi_height/height*100:.0f}%)", (10, 40), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 255, 0), 1)
cv2.imshow("底部区域掩码", bottom_roi_display)
cv2.waitKey(delay)
# INFO 边缘检测
# Apply Canny to bottom_roi instead of the full mask
edges_roi = cv2.Canny(bottom_roi, 50, 150, apertureSize=3)
if observe:
debug("步骤2: 边缘检测 (底部ROI)", "处理") # Updated text
# Displaying edges from bottom_roi directly
edges_display = cv2.cvtColor(edges_roi, cv2.COLOR_GRAY2BGR)
cv2.putText(edges_display, "Step 2: Edge Detection (Canny on Bottom ROI)", (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
cv2.putText(edges_display, "Thresholds: (50, 150)", (10, 40), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 255, 0), 1)
cv2.imshow("边缘检测 (底部ROI)", edges_display) # Updated window title
cv2.waitKey(delay)
# INFO 霍夫变换检测直线 (on edges from bottom_roi)
lines = cv2.HoughLinesP(edges_roi, 1, np.pi/180, threshold=25,
minLineLength=width*min_line_length, maxLineGap=max_line_gap)
# Adjust y-coordinates of lines detected in bottom_roi to be relative to the full image
if lines is not None:
offset_y = height - bottom_roi_height
for i in range(len(lines)):
# line[0] is [x1, y1, x2, y2]
lines[i][0][1] += offset_y # y1
lines[i][0][3] += offset_y # y2
if lines is None or len(lines) == 0:
error("未检测到直线 (在底部ROI)", "失败") # Updated error message
return None, None, None
if observe:
debug(f"步骤3: 检测到 {len(lines)} 条直线", "处理")
lines_img = img.copy()
cv2.putText(lines_img, "Step 3: Hough Lines", (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
cv2.putText(lines_img, f"Th:25, MinLen:{width*0.05:.1f}, MaxGap:{max_line_gap}", (10, 40), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 0, 255), 1)
for line in lines:
x1, y1, x2, y2 = line[0]
cv2.line(lines_img, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv2.imshow("检测到的直线", lines_img)
cv2.waitKey(delay)
# 筛选近似垂直的线
vertical_only_lines = []
for line in lines:
x1, y1, x2, y2 = line[0]
# 优先选择图像底部的线
if y1 < height * 0.4 and y2 < height * 0.4:
continue # 忽略上半部分的线
# 计算斜率 (避免除零错误)
if abs(x2 - x1) < 5: # 几乎垂直的线
slope = 100 # 设置一个较大的值表示接近垂直
else:
slope = (y2 - y1) / (x2 - x1)
# 筛选接近垂直的线 (斜率较大),但允许更多倾斜度
if abs(slope) > min_slope_threshold:
vertical_only_lines.append(line[0])
if observe:
debug(f"步骤3.2: 筛选出 {len(vertical_only_lines)} 条垂直候选线 (合并前)", "可视化")
pre_merge_lines_img = img.copy()
cv2.putText(pre_merge_lines_img, "Step 3.2: Vertical Candidates (Pre-Merge)", (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
colors = [(0, 0, 255), (0, 255, 0), (0, 255, 255), (255, 0, 0), (255, 0, 255), (255, 255, 0)]
for i, line in enumerate(vertical_only_lines):
x1, y1, x2, y2 = line
cv2.line(pre_merge_lines_img, (x1, y1), (x2, y2), colors[i % len(colors)], 2) # 使用红色显示合并前的线
cv2.imshow("合并前的垂直候选线", pre_merge_lines_img)
cv2.waitKey(delay)
if observe:
vertical_only_lines_tmp = vertical_only_lines.copy()
vertical_only_lines = _merge_collinear_lines_iterative(vertical_only_lines,
min_initial_len=5.0, # 最小初始线段长度
max_angle_diff_deg=4.0, # 最大允许角度差(度)
max_ep_gap_abs=max_line_gap, # 端点间最大绝对距离
max_ep_gap_factor=1, # 端点间最大相对距离因子
max_p_dist_abs=max_line_gap, # 点到线段最大绝对距离
max_p_dist_factor=1) # 点到线段最大相对距离因子
if observe:
print(f"合并前: {len(vertical_only_lines_tmp)} 条线, 合并后: {len(vertical_only_lines)} 条线")
debug(f"步骤3.5: 合并筛选出 {len(vertical_only_lines)} 条垂直候选线 (合并后)", "可视化")
# 创建两个图像用于对比显示
pre_merge_img = img.copy()
post_merge_img = img.copy()
# 在两张图上添加标题
cv2.putText(pre_merge_img, "合并前的垂直候选线", (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
cv2.putText(post_merge_img, "合并后的垂直候选线", (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
# 为每条线分配不同的颜色
colors = [
(255, 0, 0), # 蓝色
(0, 255, 0), # 绿色
(0, 0, 255), # 红色
(255, 255, 0), # 青色
(255, 0, 255), # 品红
(0, 255, 255), # 黄色
]
# 绘制合并前的线
for i, line in enumerate(vertical_only_lines_tmp):
x1, y1, x2, y2 = line
color = colors[i % len(colors)]
cv2.line(pre_merge_img, (x1, y1), (x2, y2), color, 2)
mid_x = (x1 + x2) // 2
mid_y = (y1 + y2) // 2
cv2.putText(pre_merge_img, str(i+1), (mid_x, mid_y),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1)
# 绘制合并后的线
for i, line in enumerate(vertical_only_lines):
x1, y1, x2, y2 = line[0]
color = colors[i % len(colors)]
cv2.line(post_merge_img, (x1, y1), (x2, y2), color, 2)
mid_x = (x1 + x2) // 2
mid_y = (y1 + y2) // 2
cv2.putText(post_merge_img, str(i+1), (mid_x, mid_y),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1)
# 水平拼接两张图片
combined_img = np.hstack((pre_merge_img, post_merge_img))
cv2.imshow("合并前后的垂直候选线对比", combined_img)
cv2.waitKey(delay)
vertical_lines = []
for line in vertical_only_lines:
x1, y1, x2, y2 = line[0]
# 优先选择图像底部的线
if y1 < height * 0.4 and y2 < height * 0.4:
continue # 忽略上半部分的线
# 计算斜率 (避免除零错误)
if abs(x2 - x1) < 5: # 几乎垂直的线
slope = 100 # 设置一个较大的值表示接近垂直
else:
slope = (y2 - y1) / (x2 - x1)
# 筛选接近垂直的线 (斜率较大),但允许更多倾斜度
if abs(slope) > min_slope_threshold:
line_length = np.sqrt((x2-x1)**2 + (y2-y1)**2)
# 计算线的中点x坐标
mid_x = (x1 + x2) / 2
# 计算线的中点y坐标
mid_y = (y1 + y2) / 2
# 保存线段、其坐标、斜率和长度
vertical_lines.append((line[0], mid_x, mid_y, slope, line_length))
if len(vertical_lines) < 2:
if len(vertical_lines) < 2:
error("未检测到足够的垂直线", "失败")
return None, None, None
if observe:
debug(f"步骤4: 找到 {len(vertical_lines)} 条垂直线", "处理")
v_lines_img = img.copy()
cv2.putText(v_lines_img, "Step 4: Vertical Lines Filtered", (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
cv2.putText(v_lines_img, f"Min Slope Abs: {min_slope_threshold:.2f}", (10, 40), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 0, 255), 1)
for line_info in vertical_lines:
line, _, _, slope, _ = line_info
x1, y1, x2, y2 = line
cv2.line(v_lines_img, (x1, y1), (x2, y2), (0, 255, 255), 2)
# 显示斜率
cv2.putText(v_lines_img, f"{slope:.2f}", ((x1+x2)//2, (y1+y2)//2),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1)
cv2.imshow("垂直线", v_lines_img)
cv2.waitKey(delay)
# 按x坐标将线分为左右两组
left_lines = [line for line in vertical_lines if line[1] < center_x]
right_lines = [line for line in vertical_lines if line[1] > center_x]
# 如果任一侧没有检测到线,则放宽左右两侧线的分组条件
if not left_lines or not right_lines:
# 按x坐标排序所有垂直线
vertical_lines.sort(key=lambda x: x[1])
# 如果有至少两条线,将最左侧的线作为左轨迹线,最右侧的线作为右轨迹线
if len(vertical_lines) >= 2:
left_lines = [vertical_lines[0]]
right_lines = [vertical_lines[-1]]
else:
error("左侧或右侧未检测到轨迹线", "失败")
return None, None, None
if observe:
debug(f"左侧候选线数量: {len(left_lines)}, 右侧候选线数量: {len(right_lines)}", "线候选")
# 创建左右线可视化图像
left_right_img = img.copy()
cv2.putText(left_right_img, "Left and Right Candidate Lines", (10, 20),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
# 绘制左侧候选线(蓝色)
for line_info in left_lines:
line = line_info[0]
x1, y1, x2, y2 = line
cv2.line(left_right_img, (x1, y1), (x2, y2), (255, 0, 0), 2)
# 显示斜率
mid_x = (x1 + x2) // 2
mid_y = (y1 + y2) // 2
cv2.putText(left_right_img, f"L:{line_info[3]:.2f}", (mid_x, mid_y),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1)
# 绘制右侧候选线(红色)
for line_info in right_lines:
line = line_info[0]
x1, y1, x2, y2 = line
cv2.line(left_right_img, (x1, y1), (x2, y2), (0, 0, 255), 2)
# 显示斜率
mid_x = (x1 + x2) // 2
mid_y = (y1 + y2) // 2
cv2.putText(left_right_img, f"R:{line_info[3]:.2f}", (mid_x, mid_y),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1)
cv2.imshow("左右候选线", left_right_img)
cv2.waitKey(delay)
# 优化说明:在默认模式下,评分函数和线对选择都优先考虑更靠近图像中心的线段
# 这有助于减少对图像边缘可能出现的干扰线的选择,提高轨道线检测的准确性
# INFO 改进的评分函数 - 同时考虑斜率、位置、长度和在图像中的位置
def score_line(line_info, is_left):
_, mid_x, mid_y, slope, length = line_info
line_points = line_info[0] # 获取线段的端点坐标
x1, y1, x2, y2 = line_points
# 确保线段从上到下排序
if y1 > y2:
x1, x2 = x2, x1
y1, y2 = y2, y1
# 线段靠近底部评分 - y越大越靠近底部分数越高
bottom_ratio = mid_y / height
y_score = min(1.0, bottom_ratio * 1.2) # 适当提高底部线段的权重
# 线段长度评分 - 线越长分数越高
length_ratio = length / (height * 0.3)
length_score = min(1.0, length_ratio * 1.2)
# 计算到图像中心的距离得分 - 更重视靠近垂直中线的线
center_x = width / 2
distance_to_center = abs(mid_x - center_x)
# 使用更陡峭的衰减函数,使得距离中心越近的线得分越高
center_proximity_score = max(0, 1.0 - (distance_to_center / (width * 0.25)) ** 2)
# 计算底部点与中心的偏差
bottom_x = x1
if abs(y2 - y1) > 1: # 非水平线
t = (height - y1) / (y2 - y1)
if t >= 0: # 确保是向下延伸
bottom_x = x1 + t * (x2 - x1)
bottom_x_distance = abs(bottom_x - center_x)
bottom_x_score = max(0, 1.0 - (bottom_x_distance / (width * 0.25)) ** 2)
# 斜率评分 - 轨道线应该有一定的倾斜度
if abs(slope) > 5:
slope_score = 0.3
else:
ideal_slope_magnitude = 0.8
ideal_slope = -ideal_slope_magnitude if is_left else ideal_slope_magnitude
if (is_left and slope > 0) or (not is_left and slope < 0):
slope_sign_score = 0.3
else:
slope_sign_score = 1.0
slope_diff = abs(slope - ideal_slope)
slope_magnitude_score = max(0, 1.0 - slope_diff / 2.0)
slope_score = slope_sign_score * slope_magnitude_score
# 检查线段是否从底部区域开始
bottom_region_threshold = height * 0.7
reaches_bottom = max(y1, y2) > bottom_region_threshold
bottom_reach_score = 1.0 if reaches_bottom else 0.5
# 综合评分 - 大幅提高中心接近性的权重
final_score = (
y_score * 0.1 + # 底部接近度
length_score * 0.05 + # 线段长度
center_proximity_score * 0.6 + # 与中心的接近度 (权重提高)
bottom_x_score * 0.15 + # 底部点位置
slope_score * 0.05 + # 斜率合适性
bottom_reach_score * 0.05 # 是否到达底部
)
return final_score
# 如果有多条线,评估左右线对是否平行
if len(left_lines) > 0 and len(right_lines) > 0:
# 计算最佳的左右线对
best_pair_score = -1
best_left_line = None
best_right_line = None
# 先对左右线按照评分排序,只考虑评分较高的候选线(减少计算量)
left_lines = sorted(left_lines, key=lambda line: score_line(line, True), reverse=True)
right_lines = sorted(right_lines, key=lambda line: score_line(line, False), reverse=True)
left_line = left_lines[0] if left_lines else None
right_line = right_lines[0] if right_lines else None
if observe:
debug(f"选择最佳线对,评分: {best_pair_score:.2f}", "线对")
# 创建评分可视化图像
score_img = img.copy()
cv2.putText(score_img, "Line Scores Visualization", (10, 20),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
# 显示左侧线的评分
for i, line_info in enumerate(left_lines):
line = line_info[0]
x1, y1, x2, y2 = line
score = score_line(line_info, True)
cv2.line(score_img, (x1, y1), (x2, y2), (255, 0, 0), 2)
mid_x = (x1 + x2) // 2
mid_y = (y1 + y2) // 2
cv2.putText(score_img, f"L{i+1}:{score:.2f}", (mid_x, mid_y),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1)
# 显示右侧线的评分
for i, line_info in enumerate(right_lines):
line = line_info[0]
x1, y1, x2, y2 = line
score = score_line(line_info, False)
cv2.line(score_img, (x1, y1), (x2, y2), (0, 0, 255), 2)
mid_x = (x1 + x2) // 2
mid_y = (y1 + y2) // 2
cv2.putText(score_img, f"R{i+1}:{score:.2f}", (mid_x, mid_y),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1)
cv2.imshow("线评分可视化", score_img)
cv2.waitKey(delay)
else:
# 如果只有单侧有线,使用评分最高的线
left_lines = sorted(left_lines, key=lambda line: score_line(line, True), reverse=True)
right_lines = sorted(right_lines, key=lambda line: score_line(line, False), reverse=True)
left_line = left_lines[0] if left_lines else None
right_line = right_lines[0] if right_lines else None
# 确保两侧都有线
if left_line is None or right_line is None:
error("无法确定合适的左右轨迹线对", "失败")
return None, None, None
# 获取两条线的坐标
left_x1, left_y1, left_x2, left_y2 = left_line[0]
right_x1, right_y1, right_x2, right_y2 = right_line[0]
# 确保线段的顺序是从上到下
if left_y1 > left_y2:
left_x1, left_x2 = left_x2, left_x1
left_y1, left_y2 = left_y2, left_y1
if right_y1 > right_y2:
right_x1, right_x2 = right_x2, right_x1
right_y1, right_y2 = right_y2, right_y1
# 尝试延长线段到图像底部,处理被石板路部分遮挡的情况
left_extended_y2 = height
if abs(left_x2 - left_x1) < 5: # 几乎垂直
left_extended_x2 = left_x2
else:
left_slope = (left_y2 - left_y1) / (left_x2 - left_x1)
left_extended_x2 = left_x1 + (left_extended_y2 - left_y1) / left_slope
right_extended_y2 = height
if abs(right_x2 - right_x1) < 5: # 几乎垂直
right_extended_x2 = right_x2
else:
right_slope = (right_y2 - right_y1) / (right_x2 - right_x1)
right_extended_x2 = right_x1 + (right_extended_y2 - right_y1) / right_slope
# 更新线段端点为延长后的坐标
left_x2, left_y2 = int(left_extended_x2), left_extended_y2
right_x2, right_y2 = int(right_extended_x2), right_extended_y2
# 尝试延长线段到图像底部,处理被石板路部分遮挡的情况
left_extended_y2 = height
if abs(left_x2 - left_x1) < 5: # 几乎垂直
left_extended_x2 = left_x2
else:
left_slope = (left_y2 - left_y1) / (left_x2 - left_x1)
left_extended_x2 = left_x1 + (left_extended_y2 - left_y1) / left_slope
right_extended_y2 = height
if abs(right_x2 - right_x1) < 5: # 几乎垂直
right_extended_x2 = right_x2
else:
right_slope = (right_y2 - right_y1) / (right_x2 - right_x1)
right_extended_x2 = right_x1 + (right_extended_y2 - right_y1) / right_slope
# 更新线段端点为延长后的坐标
left_x2, left_y2 = int(left_extended_x2), left_extended_y2
right_x2, right_y2 = int(right_extended_x2), right_extended_y2
# 改进的中心线计算方法
# 首先确定两条轨迹线的有效部分 - 以两条线段的y坐标重叠部分为准
min_y = max(left_y1, right_y1)
max_y = min(left_y2, right_y2)
# 如果两条线没有重叠的y部分则使用整个图像范围
if min_y >= max_y:
min_y = 0
max_y = height
# 计算中间路径点的方式:在多个高度上计算左右线的中点,然后拟合中心线
num_points = 20 # 增加采样点数量以提高精度
center_points = []
# 优化采样策略 - 在底部区域采样更密集
sample_ys = []
# 前半部分采样点均匀分布
first_half = np.linspace(min_y, min_y + (max_y - min_y) * 0.5, num_points // 4)
# 后半部分(更靠近底部)采样点更密集
second_half = np.linspace(min_y + (max_y - min_y) * 0.5, max_y, num_points - num_points // 4)
sample_ys = np.concatenate([first_half, second_half])
for y in sample_ys:
# 计算左侧线在当前y值处的x坐标
if abs(left_y2 - left_y1) < 1: # 防止除零
left_x = left_x1
else:
t = (y - left_y1) / (left_y2 - left_y1)
left_x = left_x1 + t * (left_x2 - left_x1)
# 计算右侧线在当前y值处的x坐标
if abs(right_y2 - right_y1) < 1: # 防止除零
right_x = right_x1
else:
t = (y - right_y1) / (right_y2 - right_y1)
right_x = right_x1 + t * (right_x2 - right_x1)
# 计算中心点
center_x = (left_x + right_x) / 2
center_points.append((center_x, y))
# 使用采样的中心点拟合中心线
center_xs = np.array([p[0] for p in center_points])
center_ys = np.array([p[1] for p in center_points])
# 使用多项式拟合改进中心线 - 选择合适的多项式阶数
if len(center_points) >= 5: # 需要更多点来拟合更高阶多项式
try:
# 尝试不同阶数的多项式拟合,选择最佳结果
best_poly_coeffs = None
best_error = float('inf')
for degree in [1, 2, 3]: # 尝试1-3阶多项式
try:
# 使用多项式拟合
poly_coeffs = np.polyfit(center_ys, center_xs, degree)
poly_func = np.poly1d(poly_coeffs)
# 计算拟合误差
predicted_xs = poly_func(center_ys)
err = np.mean(np.abs(predicted_xs - center_xs))
# 如果误差更小,更新最佳拟合
if err < best_error:
best_error = err
best_poly_coeffs = poly_coeffs
except:
continue
# 如果找到了有效的拟合,使用它
if best_poly_coeffs is not None:
poly_coeffs = best_poly_coeffs
poly_func = np.poly1d(poly_coeffs)
# 为了提高底部拟合精度,给予底部区域更高的权重重新拟合
weights = np.ones_like(center_ys)
# 计算距离底部的归一化距离 (0表示底部1表示顶部)
normalized_distance = (max_y - center_ys) / max(1, (max_y - min_y))
# 设置权重,底部权重更高
weights = 1.0 + 2.0 * (1.0 - normalized_distance)
# 使用加权多项式拟合
try:
weighted_poly_coeffs = np.polyfit(center_ys, center_xs, len(best_poly_coeffs) - 1, w=weights)
weighted_poly_func = np.poly1d(weighted_poly_coeffs)
# 比较加权拟合与原始拟合
weighted_predicted_xs = weighted_poly_func(center_ys)
weighted_error = np.mean(np.abs(weighted_predicted_xs - center_xs))
# 如果加权拟合更好,则使用它
if weighted_error < best_error * 1.2: # 允许一定的误差增加,因为我们更关注底部精度
poly_coeffs = weighted_poly_coeffs
poly_func = weighted_poly_func
except:
pass # 如果加权拟合失败,继续使用未加权的拟合
# 使用多项式生成中心线的起点和终点
center_line_y1 = min_y
center_line_y2 = height # 延伸到图像底部
# 计算多项式在这些y值处的x坐标
center_line_x1 = poly_func(center_line_y1)
center_line_x2 = poly_func(center_line_y2)
# 计算中心线在图像底部的x坐标 - 用于计算偏离度
bottom_x = poly_func(height)
# 确保坐标在图像范围内
bottom_x = max(0, min(width - 1, bottom_x))
center_point = (int(bottom_x), int(height))
# 计算中心线的加权平均斜率 - 更关注底部区域的斜率
if abs(center_line_y2 - center_line_y1) < 1: # 防止除零
center_slope = 0
else:
# 计算全局斜率
global_slope = (center_line_x2 - center_line_x1) / (center_line_y2 - center_line_y1)
# 计算底部区域的斜率,使用多个点来提高精度
bottom_slopes = []
bottom_region_start = height - height * 0.3 # 底部30%区域
if bottom_region_start > min_y:
# 在底部区域采样多个点计算局部斜率
bottom_sample_count = 5
bottom_ys = np.linspace(bottom_region_start, height, bottom_sample_count)
for i in range(len(bottom_ys) - 1):
y1, y2 = bottom_ys[i], bottom_ys[i+1]
x1, x2 = poly_func(y1), poly_func(y2)
# 确保坐标有效
x1 = max(0, min(width - 1, x1))
x2 = max(0, min(width - 1, x2))
local_slope = (x2 - x1) / max(0.1, (y2 - y1))
bottom_slopes.append(local_slope)
# 计算底部斜率的加权平均值,越靠近底部权重越高
if bottom_slopes:
weights = np.linspace(1, 2, len(bottom_slopes))
bottom_slope = np.average(bottom_slopes, weights=weights)
# 加权平均,底部斜率权重更高
center_slope = bottom_slope * 0.8 + global_slope * 0.2
else:
center_slope = global_slope
else:
center_slope = global_slope
else:
# 如果所有多项式拟合都失败,退回到简单的线性拟合
raise Exception("多项式拟合失败")
except Exception as e:
warning(f"多项式拟合失败,使用简单中点计算: {e}", "拟合")
# 如果多项式拟合失败,退回到简单的中点计算方法
center_line_x1 = (left_x1 + right_x1) / 2
center_line_y1 = (left_y1 + right_y1) / 2
center_line_x2 = (left_x2 + right_x2) / 2
center_line_y2 = (left_y2 + right_y2) / 2
# 计算中心线的斜率
if abs(center_line_x2 - center_line_x1) < 5:
center_slope = 100 # 几乎垂直
else:
center_slope = (center_line_y2 - center_line_y1) / (center_line_x2 - center_line_x1)
# 计算中心线延伸到图像底部的点
if abs(center_slope) < 0.01: # 几乎水平
bottom_x = center_line_x1
else:
bottom_x = center_line_x1 + (height - center_line_y1) / center_slope
bottom_x = max(0, min(width - 1, bottom_x))
center_point = (int(bottom_x), int(height))
else:
# 如果点数不足,退回到简单的中点计算方法
center_line_x1 = (left_x1 + right_x1) / 2
center_line_y1 = (left_y1 + right_y1) / 2
center_line_x2 = (left_x2 + right_x2) / 2
center_line_y2 = (left_y2 + right_y2) / 2
# 计算中心线的斜率
if abs(center_line_x2 - center_line_x1) < 5:
center_slope = 100 # 几乎垂直
else:
center_slope = (center_line_y2 - center_line_y1) / (center_line_x2 - center_line_x1)
# 计算中心线延伸到图像底部的点
if abs(center_slope) < 0.01: # 几乎水平
bottom_x = center_line_x1
else:
bottom_x = center_line_x1 + (height - center_line_y1) / center_slope
bottom_x = max(0, min(width - 1, bottom_x))
center_point = (int(bottom_x), int(height))
# 计算中心线与图像中心线的偏差
deviation = width / 2 - bottom_x
result_img = None
if observe or save_log:
result_img = img.copy()
# 绘制左右轨迹线
try:
# 确保坐标在图像范围内并且是整数
left_x1_safe = max(0, min(width - 1, left_x1))
left_y1_safe = max(0, min(height - 1, left_y1))
left_x2_safe = max(0, min(width - 1, left_x2))
left_y2_safe = max(0, min(height - 1, left_y2))
cv2.line(result_img, (int(left_x1_safe), int(left_y1_safe)),
(int(left_x2_safe), int(left_y2_safe)), (255, 0, 0), 2)
except Exception as e:
warning(f"绘制左轨迹线错误: {e}", "绘图")
try:
# 确保坐标在图像范围内并且是整数
right_x1_safe = max(0, min(width - 1, right_x1))
right_y1_safe = max(0, min(height - 1, right_y1))
right_x2_safe = max(0, min(width - 1, right_x2))
right_y2_safe = max(0, min(height - 1, right_y2))
cv2.line(result_img, (int(right_x1_safe), int(right_y1_safe)),
(int(right_x2_safe), int(right_y2_safe)), (0, 0, 255), 2)
except Exception as e:
warning(f"绘制右轨迹线错误: {e}", "绘图")
# 绘制中心线 - 如果有多项式拟合,绘制拟合曲线
if len(center_points) >= 3:
# 绘制拟合的中心曲线
try:
curve_ys = np.linspace(min_y, height, 100)
curve_xs = poly_func(curve_ys)
for i in range(len(curve_ys) - 1):
try:
# 确保坐标在图像范围内并且是整数
x1 = max(0, min(width - 1, curve_xs[i]))
y1 = max(0, min(height - 1, curve_ys[i]))
x2 = max(0, min(width - 1, curve_xs[i+1]))
y2 = max(0, min(height - 1, curve_ys[i+1]))
pt1 = (int(x1), int(y1))
pt2 = (int(x2), int(y2))
cv2.line(result_img, pt1, pt2, (0, 255, 0), 2)
except Exception as e:
warning(f"绘制曲线段错误: {e}", "绘图")
continue
# 绘制采样点
for pt in center_points:
try:
# 确保坐标在图像范围内并且是整数
x = max(0, min(width - 1, pt[0]))
y = max(0, min(height - 1, pt[1]))
cv2.circle(result_img, (int(x), int(y)), 3, (0, 255, 255), -1)
except Exception as e:
warning(f"绘制采样点错误: {e}", "绘图")
continue
except Exception as e:
warning(f"绘制曲线错误: {e}", "绘图")
else:
# 绘制简单中心线
try:
# 确保坐标在图像范围内并且是整数
x1 = max(0, min(width - 1, center_line_x1))
y1 = max(0, min(height - 1, center_line_y1))
x2 = max(0, min(width - 1, center_line_x2))
y2 = max(0, min(height - 1, center_line_y2))
cv2.line(result_img, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2)
except Exception as e:
warning(f"绘制中心线错误: {e}", "绘图")
# 绘制图像中心线
try:
center_x_safe = max(0, min(width - 1, center_x))
cv2.line(result_img, (int(center_x_safe), 0), (int(center_x_safe), height), (0, 0, 255), 1)
except Exception as e:
warning(f"绘制图像中心线错误: {e}", "绘图")
# 标记中心点
try:
# 确保中心点坐标在图像范围内
center_x_safe = max(0, min(width - 1, center_point[0]))
center_y_safe = max(0, min(height - 1, center_point[1]))
cv2.circle(result_img, (int(center_x_safe), int(center_y_safe)), 10, (255, 0, 255), -1)
except Exception as e:
warning(f"绘制中心点错误: {e}", "绘图")
# 显示偏差信息
cv2.putText(result_img, f"Deviation: {deviation:.1f}px", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
cv2.putText(result_img, "Final Result", (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
if 'best_pair_score' in locals() and best_pair_score != -1:
cv2.putText(result_img, f"Pair Score: {best_pair_score:.2f}", (10, 85), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)
current_y_offset = 105
else:
current_y_offset = 85
cv2.putText(result_img, f"L-Slope: {left_line[3]:.2f}, R-Slope: {right_line[3]:.2f}", (10, current_y_offset), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)
current_y_offset += 20
cv2.putText(result_img, f"Track Width: {right_line[1] - left_line[1]:.1f}px", (10, current_y_offset), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)
if observe:
cv2.imshow("轨迹线检测结果", result_img)
cv2.waitKey(delay)
# 保存日志图像
if save_log and result_img is not None:
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S_%f")
log_dir = "logs/image"
os.makedirs(log_dir, exist_ok=True)
# 保存结果图像
result_img_path = os.path.join(log_dir, f"dual_track_{timestamp}.jpg")
cv2.imwrite(result_img_path, result_img)
# 保存原图
orig_img_path = os.path.join(log_dir, f"dual_track_orig_{timestamp}.jpg")
cv2.imwrite(orig_img_path, img)
info(f"保存双轨迹线检测结果图像到: {result_img_path}", "日志")
info(f"保存原始图像到: {orig_img_path}", "日志")
# 保存文本日志信息
log_info = {
"timestamp": timestamp,
"center_point": (int(center_point[0]), int(center_point[1])),
"deviation": float(deviation),
"left_track_mid_x": float(left_line[1]),
"right_track_mid_x": float(right_line[1]),
"track_width": float(right_line[1] - left_line[1]),
"center_slope": float(center_slope),
}
info(f"双轨迹线检测结果: {log_info}", "日志")
# 创建左右轨迹线和中心线信息
left_track_info = {
"line": (left_x1, left_y1, left_x2, left_y2),
"slope": left_line[3],
"x_mid": left_line[1]
}
right_track_info = {
"line": (right_x1, right_y1, right_x2, right_y2),
"slope": right_line[3],
"x_mid": right_line[1]
}
center_info = {
"point": (int(center_point[0]), int(center_point[1])),
"deviation": float(deviation),
"slope": float(center_slope),
"is_vertical": abs(center_slope) > 5.0, # 判断是否接近垂直
"track_width": float(right_line[1] - left_line[1]), # 两轨迹线之间的距离
}
return center_info, left_track_info, right_track_info