From f5774d97b9eefe476c2dd500290308ba3d8cad9b Mon Sep 17 00:00:00 2001 From: Havoc <2993167370@qq.com> Date: Thu, 15 May 2025 20:16:57 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20=E6=9B=B4=E6=96=B0=E5=9B=BE?= =?UTF-8?q?=E5=83=8F=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91=EF=BC=8C=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E8=BE=B9=E7=BC=98=E6=A3=80=E6=B5=8B=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改 yellow_track_demo.py 中的输入和输出路径,确保使用最新的图像文件 - 在 detect_track.py 中改进掩码处理,添加形态学操作以提升掩码质量 - 优化轮廓检测逻辑,增加边缘检测和直线拟合的步骤,确保检测到的线段更准确 - 更新了相关的观察输出信息,便于调试和分析 --- .DS_Store | Bin 6148 -> 6148 bytes res/path/test/edge_img.png | Bin 55454 -> 79821 bytes test/task-path-track/yellow_track_demo.py | 4 +- utils/detect_track.py | 483 ++++++++-------------- 4 files changed, 176 insertions(+), 311 deletions(-) diff --git a/.DS_Store b/.DS_Store index 1870410e0157552b40b44f2478991a55580b303e..918d4742b1efed05b0436cad42f04cb33c614bc4 100644 GIT binary patch delta 138 zcmZoMXfc=|&e%4wP>hv>fq{WzVxfpE6OaJ{AexbZL4biFo*|bZpP`H)wJdmIp}IUs zf|DVMA(5e&Ar)D&C@&{JFMXnh{Kj-U_Q?h!EStGGcsLl_HWq$op3E6**OF_7*#i3{LVa?Uqq1; VB%c6MI@yFrd2@ou66T2wEC7C(5b^*3 diff --git a/res/path/test/edge_img.png b/res/path/test/edge_img.png index 00d105d8425336d35bd9ecb70bef75021b9ae69c..ada675f044aa3cc83f6920ab5a837a9f4da2805a 100644 GIT binary patch literal 79821 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�M6(>%fz}ahFZouL?2S( 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 CMk=TP literal 55454 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 dp9$lXQV)H9E0B>0Gn1WLbGGa~`G5N9KyCm4 diff --git a/test/task-path-track/yellow_track_demo.py b/test/task-path-track/yellow_track_demo.py index 82c30dc..92bdd73 100644 --- a/test/task-path-track/yellow_track_demo.py +++ b/test/task-path-track/yellow_track_demo.py @@ -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/result_image_20250513_162556.png', help='输出结果的保存路径') + parser.add_argument('--input', type=str, default='res/path/image_20250514_024313.png', help='输入图像或视频的路径') + parser.add_argument('--output', type=str, default='res/path/test/result_image_20250514_024313.png', 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 705cfd1..2f2054e 100644 --- a/utils/detect_track.py +++ b/utils/detect_track.py @@ -1,7 +1,5 @@ import cv2 import numpy as np -from sklearn.cluster import KMeans -from sklearn.metrics import silhouette_score from sklearn import linear_model def detect_horizontal_track_edge(image, observe=False, delay=1000): @@ -58,21 +56,15 @@ def detect_horizontal_track_edge(image, observe=False, delay=1000): # 创建黄色的掩码 mask = cv2.inRange(hsv, lower_yellow, upper_yellow) + # 添加形态学操作以改善掩码 + kernel = np.ones((3, 3), np.uint8) + mask = cv2.dilate(mask, kernel, iterations=1) + 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) @@ -81,190 +73,181 @@ def detect_horizontal_track_edge(image, observe=False, delay=1000): cv2.imshow("只保留黄色", yellow_only) cv2.waitKey(delay) - # 查找轮廓 - contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + # 裁剪掩码到搜索区域 + search_mask = mask[top_bound:bottom_bound, left_bound:right_bound] - # 如果没有找到轮廓,返回None - if not contours: + # 找到掩码在搜索区域中最底部的非零点位置 + bottom_points = [] + non_zero_cols = np.where(np.any(search_mask, axis=0))[0] + + # 寻找每列的最底部点 + for col in non_zero_cols: + col_points = np.where(search_mask[:, col] > 0)[0] + if len(col_points) > 0: + bottom_row = np.max(col_points) + bottom_points.append((left_bound + col, top_bound + bottom_row)) + + if len(bottom_points) < 3: + # 如果找不到足够的底部点,使用canny+霍夫变换 + edges = cv2.Canny(mask, 50, 150, apertureSize=3) + 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) + print("步骤3.1: 边缘检测") + cv2.imshow("边缘检测", edges) + cv2.waitKey(delay) - # 计算轮廓的宽高比 - aspect_ratio = float(w) / max(h, 1) + # 使用霍夫变换检测直线 - 调低阈值以检测短线段 + lines = cv2.HoughLinesP(edges, 1, np.pi/180, threshold=30, + minLineLength=width*0.1, maxLineGap=30) + + if lines is None or len(lines) == 0: + if observe: + print("未检测到直线") + return None, None - # 在搜索区域内且宽高比大于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("未找到符合条件的横向轮廓") + print(f"步骤4: 检测到 {len(lines)} 条直线") + lines_img = img.copy() + 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) + + # 筛选水平线,但放宽斜率条件 + horizontal_lines = [] + for line in lines: + x1, y1, x2, y2 = line[0] + + # 计算斜率 (避免除零错误) + if abs(x2 - x1) < 5: # 几乎垂直的线 + continue + + slope = (y2 - y1) / (x2 - x1) + + # 筛选接近水平的线 (斜率接近0),但容许更大的倾斜度 + if abs(slope) < 0.3: + # 确保线在搜索区域内 + if ((left_bound <= x1 <= right_bound and top_bound <= y1 <= bottom_bound) or + (left_bound <= x2 <= right_bound and top_bound <= y2 <= bottom_bound)): + # 计算线的中点y坐标 + mid_y = (y1 + y2) / 2 + line_length = np.sqrt((x2-x1)**2 + (y2-y1)**2) + # 保存线段、其y坐标和长度 + horizontal_lines.append((line[0], mid_y, slope, line_length)) + + if not horizontal_lines: + if observe: + print("未检测到水平线") + return None, None - # 如果没有找到符合条件的横向轮廓,尝试使用所有在搜索区域内的轮廓 - 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] + print(f"步骤4.1: 找到 {len(horizontal_lines)} 条水平线") + h_lines_img = img.copy() + for line_info in horizontal_lines: + line, _, slope, _ = line_info + x1, y1, x2, y2 = line + cv2.line(h_lines_img, (x1, y1), (x2, y2), (0, 255, 255), 2) + # 显示斜率 + cv2.putText(h_lines_img, f"{slope:.2f}", ((x1+x2)//2, (y1+y2)//2), + cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1) + cv2.imshow("水平线", h_lines_img) + cv2.waitKey(delay) - 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] + # 按y坐标排序 (从大到小,底部的线排在前面) + horizontal_lines.sort(key=lambda x: x[1], reverse=True) - if current_group: - y_groups.append(current_group) + # 取最靠近底部且足够长的线作为横向赛道线 + selected_line = None + selected_slope = 0 + for line_info in horizontal_lines: + line, _, slope, length = line_info + if length > width * 0.1: # 确保线足够长 + selected_line = line + selected_slope = slope + break + + if selected_line is None and horizontal_lines: + # 如果没有足够长的线,就取最靠近底部的线 + selected_line = horizontal_lines[0][0] + selected_slope = horizontal_lines[0][2] + + if selected_line is None: + if observe: + print("无法选择合适的线段") + return None, None + + x1, y1, x2, y2 = selected_line 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 + bottom_points_img = img.copy() + for point in bottom_points: + cv2.circle(bottom_points_img, point, 3, (0, 255, 0), -1) + cv2.imshow("底部边缘点", bottom_points_img) + cv2.waitKey(delay) + + # 使用RANSAC拟合直线以去除异常值 + x_points = np.array([p[0] for p in bottom_points]).reshape(-1, 1) + y_points = np.array([p[1] for p in bottom_points]) + + # 如果点过少或分布不够宽,返回None + if len(bottom_points) < 3 or np.max(x_points) - np.min(x_points) < width * 0.1: + if observe: + print("底部点太少或分布不够宽") + return None, None + + ransac = linear_model.RANSACRegressor(residual_threshold=5.0) + ransac.fit(x_points, y_points) + + # 获取拟合参数 + selected_slope = ransac.estimator_.coef_[0] + intercept = ransac.estimator_.intercept_ + + # 检查斜率是否在合理范围内 + if abs(selected_slope) > 0.3: + if observe: + print(f"拟合斜率过大: {selected_slope:.4f}") + return None, None + + # 使用拟合的直线参数计算线段端点 + x1 = left_bound + y1 = int(selected_slope * x1 + intercept) + x2 = right_bound + y2 = int(selected_slope * x2 + intercept) + + if observe: + fitted_line_img = img.copy() + cv2.line(fitted_line_img, (x1, y1), (x2, y2), (0, 255, 255), 2) + cv2.imshow("拟合线段", fitted_line_img) + cv2.waitKey(delay) - # 找出选定组中y值最大的点(最靠近相机的点) - bottom_edge_point = max(selected_group, key=lambda p: p[1]) + # 确保x1 < x2 + if x1 > x2: + x1, x2 = x2, x1 + y1, y2 = y2, y1 + + # 找到线上y值最大的点作为边缘点(最靠近相机的点) + if y1 > y2: + bottom_edge_point = (x1, y1) + else: + bottom_edge_point = (x2, y2) + + # 获取线上的更多点 + selected_points = [] + step = 5 # 每5个像素取一个点 + for x in range(max(left_bound, int(min(x1, x2))), min(right_bound, int(max(x1, x2)) + 1), step): + y = int(selected_slope * (x - x1) + y1) + if top_bound <= y <= bottom_bound: + selected_points.append((x, y)) if observe: print(f"步骤5: 找到边缘点 {bottom_edge_point}") edge_img = img.copy() - # 绘制选定的组 - for point in selected_group: + # 画线 + cv2.line(edge_img, (x1, y1), (x2, y2), (0, 255, 0), 2) + # 绘制所有点 + for point in selected_points: cv2.circle(edge_img, point, 3, (255, 0, 0), -1) # 标记边缘点 cv2.circle(edge_img, bottom_edge_point, 10, (0, 0, 255), -1) @@ -274,119 +257,12 @@ def detect_horizontal_track_edge(image, observe=False, delay=1000): # 计算这个点到中线的距离 distance_to_center = bottom_edge_point[0] - center_x - # 改进斜率计算,使用BFS找到同一条边缘线上的更多点 - def get_better_slope(start_point, points, max_distance=20): - """使用BFS算法寻找同一条边缘线上的点,并计算更准确的斜率""" - queue = [start_point] - visited = {start_point} - line_points = [start_point] - - # BFS搜索相连的点 - while queue and len(line_points) < 200: # 增加最大点数 - current = queue.pop(0) - cx, cy = current - - # 对所有未访问点计算距离 - for point in points: - if point in visited: - continue - - px, py = point - # 计算欧氏距离 - dist = np.sqrt((px - cx) ** 2 + (py - cy) ** 2) - - # 如果距离在阈值内,认为是同一条线上的点 - # 降低距离阈值,使连接更精确 - if dist < max_distance: - queue.append(point) - visited.add(point) - line_points.append(point) - - # 如果找到足够多的点,计算斜率 - if len(line_points) >= 5: # 至少需要更多点来拟合 - x_coords = np.array([p[0] for p in line_points]) - y_coords = np.array([p[1] for p in line_points]) - - # 使用RANSAC算法拟合直线,更加鲁棒 - # 尝试使用RANSAC进行更鲁棒的拟合 - try: - # 创建RANSAC对象 - ransac = linear_model.RANSACRegressor() - X = x_coords.reshape(-1, 1) - - # 拟合模型 - ransac.fit(X, y_coords) - new_slope = ransac.estimator_.coef_[0] - - # 获取内点(符合模型的点) - inlier_mask = ransac.inlier_mask_ - inlier_points = [line_points[i] for i in range(len(line_points)) if inlier_mask[i]] - - # 至少需要3个内点 - if len(inlier_points) >= 3: - return new_slope, inlier_points - except: - # 如果RANSAC失败,回退到普通拟合 - pass - - # 标准拟合方法作为后备 - if np.std(x_coords) > 0: - new_slope, _ = np.polyfit(x_coords, y_coords, 1) - return new_slope, line_points - - return selected_slope, line_points - - # 尝试获取更准确的斜率 - improved_slope, better_line_points = get_better_slope(bottom_edge_point, selected_group) - - # 使用改进后的斜率 - slope = improved_slope - - if observe: - improved_slope_img = img.copy() - # 画出底部边缘点 - cv2.circle(improved_slope_img, bottom_edge_point, 10, (0, 0, 255), -1) - # 画出改进后找到的所有点 - for point in better_line_points: - cv2.circle(improved_slope_img, point, 3, (255, 255, 0), -1) - # 使用改进后的斜率画线 - line_length = 300 - - # 确保线条经过边缘点 - mid_x = bottom_edge_point[0] - mid_y = bottom_edge_point[1] - - # 计算线条起点和终点 - end_x = mid_x + line_length - end_y = int(mid_y + improved_slope * line_length) - start_x = mid_x - line_length - start_y = int(mid_y - improved_slope * line_length) - - # 绘制线条 - cv2.line(improved_slope_img, (start_x, start_y), (end_x, end_y), (0, 255, 0), 2) - - # 添加文本显示信息 - cv2.putText(improved_slope_img, f"原始斜率: {selected_slope:.4f}", (10, 150), - cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 2) - cv2.putText(improved_slope_img, f"改进斜率: {improved_slope:.4f}", (10, 190), - cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255), 2) - cv2.putText(improved_slope_img, f"找到点数: {len(better_line_points)}", (10, 230), - cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255), 2) - - # 显示所有原始点和改进算法选择的点之间的比较 - cv2.putText(improved_slope_img, f"原始点数: {len(selected_group)}", (10, 270), - cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 2) - - cv2.imshow("改进的斜率计算", improved_slope_img) - cv2.waitKey(delay) - # 计算中线与检测到的横向线的交点 - # 横向线方程: y = slope * (x - edge_x) + edge_y + # 横向线方程: y = slope * (x - x1) + y1 # 中线方程: x = center_x # 解这个方程组得到交点坐标 - edge_x, edge_y = bottom_edge_point intersection_x = center_x - intersection_y = slope * (center_x - edge_x) + edge_y + intersection_y = selected_slope * (center_x - x1) + y1 intersection_point = (int(intersection_x), int(intersection_y)) # 计算交点到图像底部的距离(以像素为单位) @@ -394,30 +270,19 @@ def detect_horizontal_track_edge(image, observe=False, delay=1000): if observe: slope_img = img.copy() - # 画出底部边缘点 + # 画出检测到的线 + cv2.line(slope_img, (x1, y1), (x2, y2), (0, 255, 0), 2) + # 标记边缘点 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.putText(slope_img, f"Slope: {selected_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) @@ -434,12 +299,12 @@ def detect_horizontal_track_edge(image, observe=False, delay=1000): "x": bottom_edge_point[0], "y": bottom_edge_point[1], "distance_to_center": distance_to_center, - "slope": slope, - "is_horizontal": abs(slope) < 0.05, # 判断边缘是否接近水平 - "points_count": len(selected_group), # 该组中点的数量 + "slope": selected_slope, + "is_horizontal": abs(selected_slope) < 0.05, # 判断边缘是否接近水平 + "points_count": len(selected_points), # 该组中点的数量 "intersection_point": intersection_point, # 中线与横向线的交点 "distance_to_bottom": distance_to_bottom, # 交点到图像底部的距离 - "points": selected_group # 添加选定的点组 + "points": selected_points # 添加选定的点组 } return bottom_edge_point, edge_info