活动介绍

把cv2.cornerHarris(gray,4,3,0.04)演算法流程寫出來

时间: 2025-08-16 08:12:00 浏览: 2
<think>好的,我现在需要回答用户关于OpenCV中cv2.cornerHarris函数的算法流程和实现细节的问题。首先,我应该回顾一下Harris角点检测的基本原理,确保自己理解正确。 Harris角点检测算法的核心是检测图像中的角点。角点的定义是在两个方向上都有明显变化的区域。为了检测这样的点,Harris和Stephens提出了一种方法,通过计算图像的自相关矩阵来判断每个像素是否为角点。 首先,我需要明确算法的主要步骤。通常,Harris角点检测包括以下几个步骤:计算图像在x和y方向的梯度,构建自相关矩阵M,计算每个像素的响应函数R,然后应用非极大值抑制来筛选角点。 接下来,用户提到的是cv2.cornerHarris的具体实现细节。我需要查阅OpenCV的文档或者源代码来确认具体参数和计算方式。例如,函数参数中的blockSize、ksize、k值等是如何影响计算的。 在计算梯度时,OpenCV可能使用Sobel算子来求取Ix和Iy。然后,通过窗口函数(比如高斯加权)来对梯度乘积进行加权求和,得到自相关矩阵的元素。这部分需要确认blockSize参数是否对应窗口的大小,而ksize是Sobel算子的孔径大小。 响应函数R的计算公式是R = det(M) - k*(trace(M))^2。det(M)是矩阵的行列式,trace是迹。k是一个经验值,通常在0.04到0.06之间,用户可以通过调整这个值来控制角点检测的灵敏度。 在实现细节上,cv2.cornerHarris可能对每个像素应用上述计算,然后返回响应值矩阵。用户需要对这个矩阵进行阈值处理,找到局部极大值来确定角点的位置。 另外,需要注意函数中涉及到的图像数据类型,比如输入图像必须是单通道的8位或浮点型,而响应值矩阵的类型可能是浮点型,需要适当处理才能显示或进一步使用。 还需要考虑非极大值抑制的具体实现方式。OpenCV的cornerHarris函数本身是否包含这一步,或者是否需要用户自行处理。通常,函数返回响应值后,用户需要自己应用阈值和局部极大值抑制来筛选出最终的角点。 可能遇到的问题包括:参数选择的影响,例如blockSize过大会导致角点定位不准确,k值过小可能检测到过多的边缘点等。此外,Harris角点对尺度变化敏感,可能不适用于不同尺度的图像。 最后,结合用户提供的引用内容,虽然引用中提到了特征提取和优化工具库,但可能与Harris角点检测直接关联不大。不过,引用[1]中提到的DBoW2和FAB-MAP使用了特征提取方法,可能间接涉及到角点检测作为特征提取的一部分,但用户的问题更关注于Harris算法本身,因此重点应放在算法流程和OpenCV的实现细节上。 总结回答的结构应该是先介绍算法原理,再分步骤详细说明每个部分的实现,最后讨论参数影响和使用注意事项。同时,按照用户要求,生成相关问题,可能涉及参数调整、与其他角点检测算法的比较、实际应用案例等。</think>### cv2.cornerHarris算法流程与实现细节 #### 1. **算法原理** Harris角点检测基于图像局部自相关矩阵的特征值分析,通过响应函数判断像素是否为角点[^1]。核心公式为: $$ M = \sum_{x,y} w(x,y) \begin{bmatrix} I_x^2 & I_xI_y \\ I_xI_y & I_y^2 \end{bmatrix} $$ 其中$I_x$和$I_y$是图像水平和垂直方向的梯度,$w(x,y)$为窗口函数(如高斯加权)。 响应函数$R$的计算公式: $$ R = \det(M) - k \cdot (\text{trace}(M))^2 $$ 当$R > \text{阈值}$时,判定为角点。 --- #### 2. **OpenCV实现流程** cv2.cornerHarris的具体实现步骤如下: 1. **输入图像预处理** - 输入图像需为单通道灰度图(若为彩色图需先转换为灰度图)。 - 数据类型为`uint8`或`float32`。 2. **计算梯度** 使用Sobel算子计算$I_x$和$I_y$,参数`ksize`控制Sobel核大小(默认3): ```python Ix = cv2.Sobel(src, cv2.CV_64F, 1, 0, ksize=3) Iy = cv2.Sobel(src, cv2.CV_64F, 0, 1, ksize=3) ``` 3. **构建自相关矩阵** - 对梯度乘积进行窗口加权求和,窗口大小由`blockSize`指定(默认2): ```python Ix2 = Ix * Ix Iy2 = Iy * Iy Ixy = Ix * Iy # 高斯加权(实际使用boxFilter近似) M11 = cv2.boxFilter(Ix2, cv2.CV_64F, (blockSize, blockSize)) M12 = cv2.boxFilter(Ixy, cv2.CV_64F, (blockSize, blockSize)) M22 = cv2.boxFilter(Iy2, cv2.CV_64F, (blockSize, blockSize)) ``` 4. **计算响应函数** 根据公式计算$R$,参数`k`控制灵敏度(默认0.04): ```python det = M11 * M22 - M12**2 trace = M11 + M22 R = det - k * trace**2 ``` 5. **阈值筛选与非极大值抑制** - 通过`dst = cv2.cornerHarris(...)`得到响应矩阵`R`。 - 需手动应用阈值和局部极大值抑制: ```python corners = cv2.dilate(R, None) # 膨胀强化局部极大值 mask = (corners > threshold * corners.max()) # 阈值过滤 ``` --- #### 3. **关键参数说明** | 参数 | 作用 | 典型值 | |------|------|--------| | `blockSize` | 窗口大小,影响角点检测的邻域范围 | 2~5 | | `ksize` | Sobel算子的孔径,控制梯度计算精度 | 3(仅支持3,5,7) | | `k` | 响应函数权重,决定角点与边缘的区分度 | 0.04~0.06 | --- #### 4. **实现注意事项** - **边缘与角点的区分**:当$R<0$时为边缘,$R>0$时为角点或平坦区域。 - **尺度敏感性**:Harris算法对图像尺度敏感,需配合多尺度方法(如金字塔)处理缩放场景。 - **性能优化**:OpenCV内部使用积分图加速`boxFilter`计算,但`blockSize`过大会降低效率。 --- #### 5. **示例代码** ```python import cv2 img = cv2.imread('chessboard.png', 0) R = cv2.cornerHarris(img, blockSize=2, ksize=3, k=0.04) img[R > 0.01 * R.max()] = [0, 0, 255] # 标记角点为红色 cv2.imshow('Corners', img) cv2.waitKey(0) ``` ---
阅读全文

相关推荐

import cv2 import numpy as np from matplotlib import pyplot as plt from sklearn.cluster import KMeans import math # 纸张尺寸标准 (单位: mm) PAPER_SIZES = { "A4": (210, 297), "B5": (176, 250), "A5": (148, 210) } # 使用Harris角点检测算法检测图像中的角点 def detect_corners_with_harris(image, block_size=2, ksize=3, k=0.04, threshold=0.01): if len(image.shape) == 3: gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) else: gray = image gray = np.float32(gray) dst = cv2.cornerHarris(gray, block_size, ksize, k) dst = cv2.dilate(dst, None) corner_img = image.copy() corners = [] threshold_val = threshold * dst.max() for i in range(dst.shape[0]): for j in range(dst.shape[1]): if dst[i, j] > threshold_val: corners.append((j, i)) cv2.circle(corner_img, (j, i), 5, (0, 0, 255), 2) return corner_img, corners # 从Harris角点中找出最可能的纸张四个角点 def find_paper_corners_using_harris(corners, image_shape, top_k=4): height, width = image_shape[:2] top_left = (0, 0) top_right = (width, 0) bottom_right = (width, height) bottom_left = (0, height) candidates = { 'top_left': [], 'top_right': [], 'bottom_right': [], 'bottom_left': [] } for corner in corners: x, y = corner distances = { 'top_left': np.sqrt((x - top_left[0]) ** 2 + (y - top_left[1]) ** 2), 'top_right': np.sqrt((x - top_right[0]) ** 2 + (y - top_right[1]) ** 2), 'bottom_right': np.sqrt((x - bottom_right[0]) ** 2 + (y - bottom_right[1]) ** 2), 'bottom_left': np.sqrt((x - bottom_left[0]) ** 2 + (y - bottom_left[1]) ** 2) } closest_category = min(distances, key=distances.get) candidates[closest_category].append((corner, distances[closest_category])) selected_corners = [] for category in candidates: if candidates[category]: candidates[category].sort(key=lambda x: x[1]) selected_corners.append(candidates[category][0][0]) if len(selected_corners) < 4: return None return order_points(np.array(selected_corners)) # 对点进行排序:左上,右上,右下,左下 def order_points(pts): rect = np.zeros((4, 2), dtype="float32") s = pts.sum(axis=1) rect[0] = pts[np.argmin(s)] rect[2] = pts[np.argmax(s)] diff = np.diff(pts, axis=1) rect[1] = pts[np.argmin(diff)] rect[3] = pts[np.argmax(diff)] return rect # 检测图像中的纸张轮廓 def detect_paper_contour(image): gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) blurred = cv2.GaussianBlur(gray, (5, 5), 0) edged = cv2.Canny(blurred, 50, 200) contours, _ = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) contours = sorted(contours, key=cv2.contourArea, reverse=True)[:5] for contour in contours: peri = cv2.arcLength(contour, True) approx = cv2.approxPolyDP(contour, 0.02 * peri, True) if len(approx) == 4: return order_points(approx.reshape(4, 2)), gray return None, gray # 透视变换校正图像 def perspective_transform(image, points): def max_width_height(pts): width1 = np.sqrt(((pts[0][0] - pts[1][0]) ** 2) + ((pts[0][1] - pts[1][1]) ** 2)) width2 = np.sqrt(((pts[2][0] - pts[3][0]) ** 2) + ((pts[2][1] - pts[3][1]) ** 2)) max_width = max(int(width1), int(width2)) height1 = np.sqrt(((pts[0][0] - pts[3][0]) ** 2) + ((pts[0][1] - pts[3][1]) ** 2)) height2 = np.sqrt(((pts[1][0] - pts[2][0]) ** 2) + ((pts[1][1] - pts[2][1]) ** 2)) max_height = max(int(height1), int(height2)) return max_width, max_height points = order_points(points) max_width, max_height = max_width_height(points) dst = np.array([ [0, 0], [max_width - 1, 0], [max_width - 1, max_height - 1], [0, max_height - 1] ], dtype="float32") M = cv2.getPerspectiveTransform(points.astype("float32"), dst) warped = cv2.warpPerspective(image, M, (max_width, max_height)) return warped, M, max_width, max_height, points # 创建纸张区域掩码 def create_paper_mask(width, height): mask = np.ones((height, width), dtype=np.uint8) * 255 print(f"创建纸张掩码,尺寸: {mask.shape}, 类型: {mask.dtype}") return mask # 预处理图像以进行形状检测 def preprocess_image_for_shape_detection(image, paper_mask=None): # 转换为灰度图 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 高斯模糊去噪 blurred = cv2.GaussianBlur(gray, (5, 5), 0) # 可选:如果有纸张掩码,仅处理纸张内部区域 if paper_mask is not None: # 确保掩码尺寸与图像匹配 if paper_mask.shape[:2] != gray.shape[:2]: print(f"调整掩码尺寸: {paper_mask.shape} -> {gray.shape[:2]}") paper_mask = cv2.resize(paper_mask, (gray.shape[1], gray.shape[0]), interpolation=cv2.INTER_NEAREST) # 确保掩码是uint8类型 if paper_mask.dtype != np.uint8: print("转换掩码数据类型为uint8") paper_mask = paper_mask.astype(np.uint8) # 应用掩码 try: blurred = cv2.bitwise_and(blurred, blurred, mask=paper_mask) except cv2.error as e: print(f"应用掩码时出错: {e}") print(f"灰度图形状: {gray.shape}, 掩码形状: {paper_mask.shape}") return gray, blurred # 使用边缘检测方法检测图像中的形状区域 def detect_shapes_with_edge_detection(image, paper_mask=None, min_area_ratio=0.001, edge_canny_threshold1=30, edge_canny_threshold2=100): h, w = image.shape[:2] min_area = min_area_ratio * w * h print(f"最小面积阈值: {min_area} 像素") # 1. 调整图像大小(提升效率) if h > 500 or w > 500: scale = 500 / max(h, w) resized = cv2.resize(image, None, fx=scale, fy=scale) h_resized, w_resized = resized.shape[:2] print(f"调整图像大小: {h}x{w} -> {h_resized}x{w_resized}, 缩放比例: {scale}") else: resized = image.copy() scale = 1.0 h_resized, w_resized = h, w print(f"图像大小未调整: {h}x{w}") # 2. 多通道处理 - 结合亮度和饱和度 hsv = cv2.cvtColor(resized, cv2.COLOR_BGR2HSV) s_channel = hsv[:, :, 1] # 饱和度通道 v_channel = hsv[:, :, 2] # 亮度通道 # 3. 自适应阈值处理 _, s_binary = cv2.threshold(s_channel, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) v_binary = cv2.adaptiveThreshold(v_channel, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2) # 4. 合并两个二值图像 binary = cv2.bitwise_or(s_binary, v_binary) # 5. 形态学操作优化 kernel = np.ones((3, 3), np.uint8) binary = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel, iterations=1) # 去噪 binary = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel, iterations=2) # 填充 # 6. 应用纸张掩码 if paper_mask is not None: paper_mask_resized = cv2.resize(paper_mask, (w_resized, h_resized), interpolation=cv2.INTER_NEAREST).astype( np.uint8) binary = cv2.bitwise_and(binary, paper_mask_resized) # 7. 边缘检测 edges = cv2.Canny(binary, edge_canny_threshold1, edge_canny_threshold2) # 8. 进一步形态学操作优化边缘 edges = cv2.dilate(edges, kernel, iterations=1) edges = cv2.erode(edges, kernel, iterations=1) # 9. 提取轮廓并过滤小区域 contours, _ = cv2.findContours(edges if edges.any() else binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) valid_contours = [] for contour in contours: area = cv2.contourArea(contour) if area > min_area: # 修复轮廓坐标缩放问题 if scale != 1.0: contour[:, :, 0] = np.round(contour[:, :, 0] * (w / w_resized)).astype(int) contour[:, :, 1] = np.round(contour[:, :, 1] * (h / h_resized)).astype(int) valid_contours.append(contour) print(f" 保留轮廓,面积: {area:.1f} 像素") print(f"总共检测到 {len(valid_contours)} 个有效形状区域") return valid_contours, binary, edges # 优化角点检测,特别是针对菱形的角点 def refine_corners(contour, epsilon_factor=0.02): """优化轮廓的角点检测,特别是针对菱形等形状""" # 原始多边形近似 peri = cv2.arcLength(contour, True) approx = cv2.approxPolyDP(contour, epsilon_factor * peri, True) # 如果已经是4个点,尝试进一步优化 if len(approx) == 4: # 计算轮廓的矩 M = cv2.moments(contour) if M["m00"] != 0: cx = int(M["m10"] / M["m00"]) cy = int(M["m01"] / M["m00"]) # 将点按距离中心点的角度排序 points = approx.reshape(4, 2) angles = [] for point in points: dx = point[0] - cx dy = point[1] - cy angle = np.arctan2(dy, dx) angles.append(angle) # 按角度排序点 sorted_indices = np.argsort(angles) sorted_points = points[sorted_indices] # 重新排列为标准顺序(左上、右上、右下、左下) ordered_points = order_points(sorted_points) return ordered_points.reshape(-1, 1, 2).astype(np.int32) return approx # 识别物体形状,优化近似参数和判断逻辑 def identify_shape(contour): # 优化角点检测 approx = refine_corners(contour) num_sides = len(approx) area = cv2.contourArea(contour) # 计算轮廓周长,避免除以零 peri = cv2.arcLength(contour, True) if peri == 0: return "不规则形状" circularity = 4 * np.pi * area / (peri ** 2) if peri != 0 else 0 # 椭圆拟合(增加轮廓点数判断,避免报错) if len(contour) >= 5: try: ellipse = cv2.fitEllipse(contour) (center, axes, orientation) = ellipse major_axis = max(axes) minor_axis = min(axes) eccentricity = np.sqrt(1 - (minor_axis / major_axis) ** 2) if major_axis != 0 else 0 ellipse_area = np.pi * (major_axis / 2) * (minor_axis / 2) ellipse_ratio = area / ellipse_area if ellipse_area > 0 else 0 # 椭圆判定条件 if 0.8 <= ellipse_ratio <= 1.2 and 0.5 < eccentricity < 0.95: return "椭圆" except: pass # 多边形形状判断 if num_sides == 3: return "三角形" elif num_sides == 4: # 计算最小外接矩形 rect = cv2.minAreaRect(contour) (x, y), (width, height), angle = rect if min(width, height) == 0: # 避免除以0 aspect_ratio = 0 else: aspect_ratio = max(width, height) / min(width, height) # 计算轮廓的边界框 x, y, w, h = cv2.boundingRect(contour) bounding_ratio = max(w, h) / min(w, h) if min(w, h) > 0 else 0 # 计算四个顶点的坐标 points = approx.reshape(4, 2) # 计算相邻边之间的角度 angles = [] for i in range(4): p1 = points[i] p2 = points[(i + 1) % 4] p3 = points[(i + 2) % 4] v1 = p2 - p1 v2 = p3 - p2 dot_product = np.dot(v1, v2) cross_product = np.cross(v1, v2) angle = np.arctan2(cross_product, dot_product) * 180 / np.pi angles.append(abs(angle)) # 检查是否有直角 has_right_angle = any(abs(angle - 90) < 15 for angle in angles) # 计算边长 side_lengths = [] for i in range(4): p1 = points[i] p2 = points[(i + 1) % 4] side_length = np.sqrt((p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2) side_lengths.append(side_length) # 检查四边是否大致相等(菱形判定) avg_length = sum(side_lengths) / 4 is_rhombus = all(abs(l - avg_length) < avg_length * 0.2 for l in side_lengths) # 菱形判定条件:没有直角,四边大致相等,并且不是正方形 if not has_right_angle and is_rhombus and aspect_ratio < 1.5 and bounding_ratio > 1.2: return "菱形" # 正方形判定 if 0.85 <= aspect_ratio <= 1.15: return "正方形" # 矩形判定 else: return "矩形" elif circularity > 0.85: # 圆形判定 return "圆形" elif 5 <= num_sides <= 10: return f"{num_sides}边形" else: return "不规则形状" # 计算物体的几何属性 def calculate_properties(contour, pixel_to_mm=1.0): rect = cv2.minAreaRect(contour) width_px, height_px = rect[1] dimensions_px = sorted([width_px, height_px], reverse=True) length_px, width_px = dimensions_px length_mm = length_px * pixel_to_mm width_mm = width_px * pixel_to_mm area_px = cv2.contourArea(contour) area_mm = area_px * (pixel_to_mm ** 2) M = cv2.moments(contour) if M["m00"] != 0: cx = int(M["m10"] / M["m00"]) cy = int(M["m01"] / M["m00"]) else: cx, cy = 0, 0 return length_mm, width_mm, area_mm, (cx, cy), length_px, width_px, area_px # 提取物体主要颜色(HSV) def extract_dominant_color(image, contour): mask = np.zeros(image.shape[:2], np.uint8) cv2.drawContours(mask, [contour], -1, 255, -1) hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) # 提取掩码区域的颜色 masked_pixels = hsv[mask == 255] if len(masked_pixels) == 0: return (0, 0, 0) # 计算主导颜色 (使用中位数更鲁棒) dominant_hsv = np.median(masked_pixels, axis=0) return tuple(map(int, dominant_hsv)) # 可视化检测过程的各个步骤 def visualize_detection_steps(image, corrected_image, paper_mask, shape_regions, binary_image, edge_image, result_img): """可视化检测过程的各个步骤,每个步骤显示在单独的窗口中""" # 显示原始图像 plt.figure(figsize=(10, 8)) plt.title('Original Image') plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) plt.axis('off') plt.tight_layout() plt.show() # 显示校正后的图像 plt.figure(figsize=(10, 8)) plt.title('Corrected Image') plt.imshow(cv2.cvtColor(corrected_image, cv2.COLOR_BGR2RGB)) plt.axis('off') plt.tight_layout() plt.show() # 显示边缘检测结果 plt.figure(figsize=(10, 8)) plt.title('Edge detection Results') plt.imshow(edge_image, cmap='gray') plt.axis('off') plt.tight_layout() plt.show() # 显示二值化结果 plt.figure(figsize=(10, 8)) plt.title('Binary Image') plt.imshow(binary_image, cmap='gray') plt.axis('off') plt.tight_layout() plt.show() # 显示形状检测结果 shape_image = corrected_image.copy() colors = [(0, 0, 255), (0, 255, 0), (255, 0, 0), (255, 255, 0), (0, 255, 255), (255, 0, 255), (128, 0, 0), (0, 128, 0)] for i, contour in enumerate(shape_regions): # 绘制优化后的角点 approx = refine_corners(contour) color = colors[i % len(colors)] cv2.drawContours(shape_image, [contour.astype(int)], -1, color, 2) # 标记角点 for point in approx: x, y = point[0] cv2.circle(shape_image, (x, y), 5, (0, 255, 0), -1) plt.figure(figsize=(10, 8)) plt.title('Shape detection Results (with corners)') plt.imshow(cv2.cvtColor(shape_image, cv2.COLOR_BGR2RGB)) plt.axis('off') plt.tight_layout() plt.show() # 显示最终识别结果 plt.figure(figsize=(10, 8)) plt.title('Shape recognition Results') plt.imshow(cv2.cvtColor(result_img, cv2.COLOR_BGR2RGB)) plt.axis('off') plt.tight_layout() plt.show() # 主函数 def main(image_path): image = cv2.imread(image_path) if image is None: print(f"错误:无法读取图像 {image_path}") return # 1. 使用Harris角点检测纸张边框 corner_img, corners = detect_corners_with_harris(image) paper_points = find_paper_corners_using_harris(corners, image.shape) # 初始化变量 corrected_image = image.copy() paper_mask = None pixel_to_mm = 1.0 paper_detected = False # 2. 透视变换校正图像 if paper_points is not None: # 执行透视变换 corrected_image, M, max_width, max_height, original_points = perspective_transform(image, paper_points) paper_detected = True print("成功检测并校正纸张轮廓") # 创建纸张掩码(仅处理纸张内部区域) paper_mask = create_paper_mask(max_width, max_height) # 计算物理尺寸转换因子 paper_width_mm, paper_height_mm = PAPER_SIZES["A4"] pixel_to_mm_x = paper_width_mm / max_width pixel_to_mm_y = paper_height_mm / max_height pixel_to_mm = (pixel_to_mm_x + pixel_to_mm_y) / 2.0 else: print("未检测到纸张轮廓 - 使用原始图像进行分析") h, w = image.shape[:2] paper_mask = create_paper_mask(w, h) # 3. 基于边缘检测和形态学操作检测几何形状 shape_regions, binary_image, edge_image = detect_shapes_with_edge_detection( corrected_image, paper_mask=paper_mask, min_area_ratio=0.001, # 降低最小面积阈值 edge_canny_threshold1=30, # Canny边缘检测阈值 edge_canny_threshold2=100 # Canny边缘检测阈值 ) # 如果没有检测到形状,尝试使用不同的参数 if not shape_regions: print("第一次尝试未检测到几何形状 - 尝试使用不同的参数") shape_regions, binary_image, edge_image = detect_shapes_with_edge_detection( corrected_image, paper_mask=paper_mask, min_area_ratio=0.0005, # 进一步降低最小面积阈值 edge_canny_threshold1=20, edge_canny_threshold2=80 ) if not shape_regions: print("未检测到几何形状 - 尝试调整参数") # 备用方案:直接在二值图像上查找轮廓 gray = cv2.cvtColor(corrected_image, cv2.COLOR_BGR2GRAY) _, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU) if paper_mask is not None: binary = cv2.bitwise_and(binary, binary, mask=paper_mask) contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) min_area = 0.0005 * binary.shape[0] * binary.shape[1] shape_regions = [c for c in contours if cv2.contourArea(c) > min_area] if not shape_regions: print("所有尝试均未检测到几何形状") return else: print(f"备用方案检测到 {len(shape_regions)} 个形状") edge_image = cv2.cvtColor(binary, cv2.COLOR_GRAY2BGR) # 初始化结果列表 results = [] result_img = corrected_image.copy() # 处理每个形状区域 for i, contour in enumerate(shape_regions): # 提取主导颜色 hsv_color = extract_dominant_color(corrected_image, contour) rgb_color = cv2.cvtColor(np.uint8([[hsv_color]]), cv2.COLOR_HSV2BGR)[0][0] color_name = f"RGB({rgb_color[2]}, {rgb_color[1]}, {rgb_color[0]})" # 识别形状 shape = identify_shape(contour) # 计算几何属性 length_mm, width_mm, area_mm, center_px, length_px, width_px, area_px = calculate_properties( contour, pixel_to_mm ) # 存储结果 results.append({ "id": i + 1, "shape": shape, "length_mm": length_mm, "width_mm": width_mm, "area_mm": area_mm, "color": color_name, "hsv_color": hsv_color, "center_px": center_px, "length_px": length_px, "width_px": width_px, "area_px": area_px }) # 在图像上标注物体 color = (int(rgb_color[2]), int(rgb_color[1]), int(rgb_color[0])) cv2.drawContours(result_img, [contour], -1, color, 3) cv2.putText(result_img, f"{i + 1}({shape})", (int(center_px[0]), int(center_px[1])), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2) # 可视化检测步骤 visualize_detection_steps(image, corrected_image, paper_mask, shape_regions, binary_image, edge_image, result_img) # 打印分析结果 print("\n===== 图像分析结果 =====") print(f"检测到 {len(results)} 个几何图形") print("-" * 70) for obj in results: print(f"图形 {obj['id']}:") print(f" 形状: {obj['shape']}") print(f" 长度: {obj['length_mm']:.2f} mm ({obj['length_px']:.1f} 像素)") print(f" 宽度: {obj['width_mm']:.2f} mm ({obj['width_px']:.1f} 像素)") print(f" 面积: {obj['area_mm']:.2f} mm² ({obj['area_px']:.1f} 像素²)") print(f" 颜色: {obj['color']} (HSV: {obj['hsv_color']})") print(f" 中心坐标: ({obj['center_px'][0]}, {obj['center_px'][1]})") print("-" * 70) if __name__ == "__main__": image_path = "ALL2.jpg" # 替换为实际图像路径 main(image_path) 上述代码对图像进行二值化操作部分有点问题,导致一些几何形状的角钝化,可能因此导致形状判定不正确的问题,请修改一下这串代码,给出完整的修改后的代码

#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 机器狗 VNC 实时立方体检测 依赖: sudo apt update && sudo apt install python3-pip -y pip3 install picamera2 opencv-python numpy """ import cv2 import numpy as np import time import json import argparse import logging from picamera2 import Picamera2 from libcamera import controls logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[logging.StreamHandler(), logging.FileHandler('dog_cuboid.log')] ) logger = logging.getLogger(__name__) class DogCuboidDetector: def __init__(self, cfg): self.cfg = cfg self.picam2 = None self.init_camera() self.build_windows() # ---------- 摄像头 ---------- def init_camera(self): try: self.picam2 = Picamera2() config = self.picam2.create_preview_configuration( main={"size": self.cfg["resolution"], "format": "RGB888"}, controls={"FrameRate": self.cfg["fps"]} ) self.picam2.configure(config) self.picam2.start() time.sleep(1) logger.info(f"Camera {self.cfg['resolution']}@{self.cfg['fps']} started") except Exception as e: logger.error(f"Camera init failed: {e}") raise # ---------- 窗口 ---------- def build_windows(self): cv2.namedWindow("Live", cv2.WINDOW_NORMAL) cv2.namedWindow("Mask", cv2.WINDOW_NORMAL) cv2.resizeWindow("Live", *self.cfg["window_size"]) cv2.resizeWindow("Mask", *self.cfg["mask_size"]) # ---------- 颜色阈值 ---------- def make_masks(self, hsv): masks = {} for color, (low, high) in self.cfg["hsv_ranges"].items(): masks[color] = cv2.inRange(hsv, np.array(low), np.array(high)) # 合并 mask_all = np.zeros_like(masks["red"]) for m in masks.values(): mask_all = cv2.bitwise_or(mask_all, m) # 形态学 kernel = np.ones((3, 3), np.uint8) mask_all = cv2.morphologyEx(mask_all, cv2.MORPH_OPEN, kernel) mask_all = cv2.morphologyEx(mask_all, cv2.MORPH_CLOSE, kernel) return mask_all, masks # ---------- 形状检测 ---------- def detect_shapes(self, mask, frame): contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) objs = [] for cnt in contours: area = cv2.contourArea(cnt) if area < self.cfg["min_area"] or area > self.cfg["max_area"]: continue peri = cv2.arcLength(cnt, True) approx = cv2.approxPolyDP(cnt, 0.04 * peri, True) x, y, w, h = cv2.boundingRect(approx) aspect = float(w) / h circ = 4 * np.pi * area / (peri * peri + 1e-6) # 矩形 if len(approx) == 4 and 0.3 < aspect < 3: objs.append({"type": "rectangle", "cnt": cnt, "box": (x, y, w, h)}) # 圆柱 elif circ > 0.65: objs.append({"type": "cylinder", "cnt": cnt, "box": (x, y, w, h)}) return objs # ---------- 主循环 ---------- def run(self): logger.info("Start detection loop") fps = 0 frame_idx = 0 t0 = time.time() while True: frame = self.picam2.capture_array() hsv = cv2.cvtColor(frame, cv2.COLOR_RGB2HSV) mask_all, masks = self.make_masks(hsv) objs = self.detect_shapes(mask_all, frame) # 绘制 vis = frame.copy() for o in objs: x, y, w, h = o["box"] cv2.rectangle(vis, (x, y), (x + w, y + h), (0, 255, 0), 2) cv2.drawContours(vis, [o["cnt"]], -1, (255, 0, 0), 1) # FPS frame_idx += 1 fps = frame_idx / (time.time() - t0) cv2.putText(vis, f"FPS:{fps:.1f} Objects:{len(objs)}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 255), 2) # 显示 cv2.imshow("Live", vis) cv2.imshow("Mask", mask_all) key = cv2.waitKey(1) & 0xFF if key in (ord('q'), 27): break elif key == ord('c'): fname = f"capture_{int(time.time())}.jpg" cv2.imwrite(fname, vis) logger.info(f"Saved {fname}") self.cleanup() def cleanup(self): cv2.destroyAllWindows() if self.picam2: self.picam2.stop() logger.info("Stopped") # ---------- 配置 ---------- DEFAULT_CFG = { "resolution": [640, 480], "fps": 15, "window_size": [800, 600], "mask_size": [400, 300], "min_area": 800, "max_area": 60000, "hsv_ranges": { "red": [[0, 100, 60], [10, 255, 255]], "red2": [[170, 100, 60], [180, 255, 255]], "blue": [[100, 80, 40], [130, 255, 255]], "green": [[35, 60, 40], [85, 255, 255]] } } # ---------- 入口 ---------- if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--cfg", default="", help="JSON config file") args = parser.parse_args() cfg = DEFAULT_CFG.copy() if args.cfg and os.path.isfile(args.cfg): cfg.update(json.load(open(args.cfg))) try: DogCuboidDetector(cfg).run() except KeyboardInterrupt: logger.info("User quit") except Exception as e: logger.exception("Fatal error", exc_info=e) 参考这个代码,这个代码的效果很差,我不知道是不是用了形状优先的思路完成的。 #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Computer Camera Cuboid Detection System - Traditional Algorithm Approach Features: 1. 支持红、蓝、绿三种颜色物块识别 2. 优化红色识别,减少橙色干扰 3. 增大了最大轮廓识别面积 4. 实时桌面预览和性能统计 """ import cv2 import time import numpy as np import sys import logging # Configure logging system logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.StreamHandler(sys.stdout), logging.FileHandler('cuboid_detection.log') ] ) logger = logging.getLogger(__name__) class CuboidDetectionSystem: def __init__(self, camera_index=0): # Camera configuration self.camera_index = camera_index self.cap = None # Detection parameters self.min_area = 1000 # Minimum contour area to consider self.max_area = 150000 # Increased maximum contour area (was 50000) self.aspect_ratio_min = 0.3 # Minimum aspect ratio for rectangles self.aspect_ratio_max = 3.0 # Maximum aspect ratio for rectangles self.circularity_thresh = 0.6 # Circularity threshold for cylinders # Color detection parameters (in HSV space) # Red - optimized to reduce orange interference self.red_lower1 = np.array([0, 150, 100]) # Increased saturation and value self.red_upper1 = np.array([10, 255, 255]) self.red_lower2 = np.array([170, 150, 100]) # Increased saturation and value self.red_upper2 = np.array([180, 255, 255]) # Blue self.blue_lower = np.array([100, 120, 70]) self.blue_upper = np.array([130, 255, 255]) # Green - added for green objects self.green_lower = np.array([35, 80, 60]) # Green range self.green_upper = np.array([85, 255, 255]) # Performance tracking self.fps_history = [] self.detection_history = [] self.last_detection_time = 0 # Initialize camera self.open_camera() logger.info("Cuboid Detection System (Traditional Algorithm) initialized successfully") def open_camera(self): """Open computer's external camera""" try: self.cap = cv2.VideoCapture(self.camera_index) if not self.cap.isOpened(): # Try common alternative indices for idx in [2, 1, 0]: self.cap = cv2.VideoCapture(idx) if self.cap.isOpened(): self.camera_index = idx break if not self.cap.isOpened(): logger.error("Unable to open any camera!") return False # Set camera resolution self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) logger.info(f"Camera opened successfully at index {self.camera_index}") return True except Exception as e: logger.error(f"Camera initialization failed: {str(e)}") return False def preprocess_frame(self, frame): """Preprocess frame for contour detection""" # Convert to HSV color space hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) # Blur to reduce noise hsv = cv2.GaussianBlur(hsv, (5, 5), 0) # Create masks for red, blue and green red_mask1 = cv2.inRange(hsv, self.red_lower1, self.red_upper1) red_mask2 = cv2.inRange(hsv, self.red_lower2, self.red_upper2) red_mask = cv2.bitwise_or(red_mask1, red_mask2) blue_mask = cv2.inRange(hsv, self.blue_lower, self.blue_upper) green_mask = cv2.inRange(hsv, self.green_lower, self.green_upper) # Combine masks color_mask = cv2.bitwise_or(red_mask, blue_mask) color_mask = cv2.bitwise_or(color_mask, green_mask) # Apply morphological operations to clean up the mask kernel = np.ones((7, 7), np.uint8) color_mask = cv2.morphologyEx(color_mask, cv2.MORPH_OPEN, kernel) color_mask = cv2.morphologyEx(color_mask, cv2.MORPH_CLOSE, kernel) # Additional dilation to fill gaps color_mask = cv2.dilate(color_mask, kernel, iterations=1) return color_mask, red_mask, blue_mask, green_mask def detect_shapes(self, mask, frame): """Detect rectangular and circular shapes in the mask""" contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) detected_objects = [] for contour in contours: # Filter by area - max_area increased to 150000 area = cv2.contourArea(contour) if area < self.min_area or area > self.max_area: continue # Approximate the contour to a polygon peri = cv2.arcLength(contour, True) approx = cv2.approxPolyDP(contour, 0.04 * peri, True) # Calculate shape properties x, y, w, h = cv2.boundingRect(approx) aspect_ratio = float(w) / h circularity = 4 * np.pi * area / (peri * peri) if peri > 0 else 0 # Detect rectangles (books, boxes) if len(approx) == 4 and self.aspect_ratio_min < aspect_ratio < self.aspect_ratio_max: # Calculate the angles between consecutive edges vectors = [] for i in range(4): pt1 = approx[i][0] pt2 = approx[(i + 1) % 4][0] vectors.append(np.array(pt2) - np.array(pt1)) # Calculate angles between consecutive vectors angles = [] for i in range(4): v1 = vectors[i] v2 = vectors[(i + 1) % 4] cos_angle = np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2) + 1e-5) angle = np.arccos(np.clip(cos_angle, -1, 1)) * 180 / np.pi angles.append(angle) # Check if angles are close to 90 degrees (rectangle) if all(70 < angle < 110 for angle in angles): detected_objects.append({ 'type': 'rectangle', 'contour': contour, 'approx': approx, 'center': (x + w // 2, y + h // 2), 'box': (x, y, w, h), 'area': area }) # Detect circles/cylinders elif circularity > self.circularity_thresh: detected_objects.append({ 'type': 'cylinder', 'contour': contour, 'approx': approx, 'center': (x + w // 2, y + h // 2), 'box': (x, y, w, h), 'area': area }) return detected_objects def detect_colors(self, frame, detected_objects, red_mask, blue_mask, green_mask): """Determine the color of detected objects""" results = [] for obj in detected_objects: x, y, w, h = obj['box'] # Create mask for the object region obj_mask = np.zeros(frame.shape[:2], dtype=np.uint8) cv2.drawContours(obj_mask, [obj['contour']], -1, 255, -1) # Extract the object region from color masks obj_red = cv2.bitwise_and(red_mask, red_mask, mask=obj_mask) obj_blue = cv2.bitwise_and(blue_mask, blue_mask, mask=obj_mask) obj_green = cv2.bitwise_and(green_mask, green_mask, mask=obj_mask) # Count red, blue and green pixels in the object region red_pixels = cv2.countNonZero(obj_red) blue_pixels = cv2.countNonZero(obj_blue) green_pixels = cv2.countNonZero(obj_green) total_pixels = cv2.countNonZero(obj_mask) # Determine dominant color color = "unknown" if total_pixels > 0: red_ratio = red_pixels / total_pixels blue_ratio = blue_pixels / total_pixels green_ratio = green_pixels / total_pixels # Require at least 40% dominance for color classification if red_ratio > 0.4 and red_ratio > blue_ratio and red_ratio > green_ratio: color = "red" elif blue_ratio > 0.4 and blue_ratio > red_ratio and blue_ratio > green_ratio: color = "blue" elif green_ratio > 0.4 and green_ratio > red_ratio and green_ratio > blue_ratio: color = "green" # Add to results results.append({ 'type': obj['type'], 'color': color, 'center': obj['center'], 'box': obj['box'], 'contour': obj['contour'], 'timestamp': time.time() }) return results def detect_cuboids(self, frame): """Detect cuboid objects using traditional computer vision techniques""" # Step 1: Preprocess frame to create color mask color_mask, red_mask, blue_mask, green_mask = self.preprocess_frame(frame) # Step 2: Detect shapes detected_objects = self.detect_shapes(color_mask, frame) # Step 3: Detect colors of the shapes results = self.detect_colors(frame, detected_objects, red_mask, blue_mask, green_mask) return results, color_mask def run(self): """Main loop for desktop preview and detection""" logger.info("Starting main detection loop") window_name = "Cuboid Detection (Traditional Algorithm)" cv2.namedWindow(window_name, cv2.WINDOW_NORMAL) cv2.resizeWindow(window_name, 1200, 800) # Create mask window cv2.namedWindow("Color Mask", cv2.WINDOW_NORMAL) cv2.resizeWindow("Color Mask", 600, 400) frame_count = 0 start_time = time.time() try: while True: # Start timer for FPS calculation frame_start = time.time() # Get frame from camera ret, frame = self.cap.read() if not ret: logger.warning("Failed to capture frame") time.sleep(0.1) continue frame_count += 1 # Detect cuboids results, color_mask = self.detect_cuboids(frame) # Draw results on frame for result in results: color_type = result['color'] obj_type = result['type'] x, y, w, h = result['box'] center_x, center_y = result['center'] # Determine drawing color based on detected color if color_type == "red": color = (0, 0, 255) # Red in BGR elif color_type == "blue": color = (255, 0, 0) # Blue in BGR elif color_type == "green": color = (0, 255, 0) # Green in BGR else: color = (0, 255, 255) # Yellow for unknown # Draw bounding box cv2.rectangle(frame, (x, y), (x + w, y + h), color, 2) # Draw contour cv2.drawContours(frame, [result['contour']], -1, color, 2) # Draw center point cv2.circle(frame, (center_x, center_y), 5, color, -1) # Draw label label = f"{color_type} {obj_type}" cv2.putText(frame, label, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, color, 2) # Calculate FPS current_time = time.time() elapsed_time = current_time - start_time fps = frame_count / elapsed_time if elapsed_time > 0 else 0 # Update FPS history (keep last 30 values) self.fps_history.append(fps) if len(self.fps_history) > 30: self.fps_history.pop(0) avg_fps = sum(self.fps_history) / len(self.fps_history) if self.fps_history else 0 # Update detection history detection_status = 1 if results else 0 self.detection_history.append(detection_status) if len(self.detection_history) > 30: self.detection_history.pop(0) detection_rate = sum(self.detection_history) / len(self.detection_history) * 100 # Display performance stats stats_y = 30 cv2.putText(frame, f"FPS: {avg_fps:.1f}", (10, stats_y), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) cv2.putText(frame, f"Detection Rate: {detection_rate:.1f}%", (10, stats_y + 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2) cv2.putText(frame, f"Objects: {len(results)}", (10, stats_y + 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 200, 255), 2) # Display algorithm info cv2.putText(frame, "Algorithm: Traditional CV (Contour + Color Analysis)", (10, frame.shape[0] - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (200, 200, 200), 1) # Show frames cv2.imshow(window_name, frame) cv2.imshow("Color Mask", color_mask) # Process keyboard input key = cv2.waitKey(1) & 0xFF if key == ord('q') or key == 27: # 'q' or ESC break elif key == ord('c'): # Capture screenshot timestamp = time.strftime("%Y%m%d-%H%M%S") filename = f"capture_{timestamp}.png" cv2.imwrite(filename, frame) logger.info(f"Screenshot saved as {filename}") elif key == ord('r'): # Reset detection history self.detection_history = [] self.fps_history = [] start_time = time.time() frame_count = 0 logger.info("Reset performance counters") elif key == ord('d'): # Toggle debug mode cv2.imshow("Red Mask", self.red_mask) cv2.imshow("Green Mask", self.green_mask) cv2.imshow("Blue Mask", self.blue_mask) logger.info("Debug masks displayed") # Calculate processing time frame_time = time.time() - frame_start target_frame_time = 1 / 30 # Target 30 FPS if frame_time < target_frame_time: time.sleep(target_frame_time - frame_time) except Exception as e: logger.error(f"Main loop exception: {str(e)}", exc_info=True) finally: self.cleanup() def cleanup(self): """Clean up resources""" # Release camera if self.cap and self.cap.isOpened(): self.cap.release() logger.info("Camera resources released") # Close windows cv2.destroyAllWindows() logger.info("Program exited safely") def print_controls(): """Print program controls""" print("=" * 70) print("Computer Camera Cuboid Detection System - Traditional Algorithm") print("=" * 70) print("Detects cuboid objects (books, boxes, etc.) using computer vision techniques") print("Supports RED, BLUE and GREEN objects") print("Desktop window shows real-time preview with bounding boxes") print("Second window shows the color mask used for detection") print("=" * 70) print("Controls:") print(" q or ESC - Quit program") print(" c - Capture screenshot") print(" r - Reset performance counters") print(" d - Show debug masks (red, green, blue)") print("=" * 70) print("Detection parameters:") print(" - Red, blue and green color detection in HSV space") print(" - Rectangle detection based on polygon approximation and angle analysis") print(" - Cylinder detection based on circularity metric") print(f" - Max contour area: 150000 (was 50000)") print("=" * 70) if __name__ == "__main__": print_controls() # Default camera index (0 is usually the built-in or first external camera) camera_index = 0 # Allow specifying camera index from command line if len(sys.argv) > 1: try: camera_index = int(sys.argv[1]) print(f"Using camera index: {camera_index}") except ValueError: print(f"Invalid camera index: {sys.argv[1]}, using default (0)") try: detector = CuboidDetectionSystem(camera_index=camera_index) detector.run() except Exception as e: logger.error(f"System startup failed: {str(e)}", exc_info=True) print(f"System startup failed: {str(e)}") 比如这个代码就形状优先的完成摄像头的识别,识别效果就很好。 请你结合这两个代码,修改实现在vnc上完成机器狗识别物块!!!给出完整代码

from maix import image, camera, display, app import cv2 import numpy as np # 实例化摄像头和显示对象 cam = camera.Camera(320, 240) disp = display.Display() # 创建形态学操作核 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) def calculate_contour_center(contour): """使用轮廓矩计算精确中心点""" M = cv2.moments(contour) if M["m00"] > 0: return int(M["m10"]/M["m00"]), int(M["m01"]/M["m00"]) return None def draw_minimal_cross(image, center): """绘制小尺寸精密十字标记""" cx, cy = center # 十字线(长8px,宽1px) cv2.line(image, (cx-8, cy), (cx+8, cy), (0, 255, 255), 1) cv2.line(image, (cx, cy-8), (cx, cy+8), (0, 255, 255), 1) # 中心点(半径2px红色) cv2.circle(image, (cx, cy), 2, (0, 0, 255), -1) while not app.need_exit(): img = cam.read() img_raw = image.image2cv(img, copy=False) processed = img_raw.copy() # 图像预处理流程 gray = cv2.cvtColor(img_raw, cv2.COLOR_BGR2GRAY) blurred = cv2.GaussianBlur(gray, (5, 5), 0) edged = cv2.Canny(blurred, 50, 150) closed = cv2.morphologyEx(edged, cv2.MORPH_CLOSE, kernel) # 精确轮廓检测 contours, _ = cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 遍历前三大轮廓 for cnt in sorted(contours, key=cv2.contourArea, reverse=True)[:3]: # 多边形近似检测 peri = cv2.arcLength(cnt, True) approx = cv2.approxPolyDP(cnt, 0.015 * peri, True) # 仅处理四边形 if len(approx) == 4: # 精确中心计算 center = calculate_contour_center(cnt) if not center: continue # 绘制优化后的轮廓(青色细线) cv2.drawContours(processed, [approx], -1, (255, 255, 0), 1) # 绘制精密小十字 draw_minimal_cross(processed, center) # 显示坐标信息(小号字体) cv2.putText(processed, f"({center[0]},{center[1]})", (center[0]+10, center[1]-5), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 255, 0), 1) break # 找到第一个有效四边形后跳出循环 # 显示处理结果 img_show = image.cv2image(processed, copy=False) disp.show(img_show) 解释代码内容

from maix import image, camera, display, uart import cv2 import numpy as np # 帧头和帧尾定义 FRAME_HEADER = [0xFF, 0xFF] FRAME_TAIL = [0xFE, 0xFE] # 图像处理参数 KERNEL = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) MIN_AREA = 200 MAX_AREA = 20000 CANNY_THRESH1 = 80 CANNY_THRESH2 = 150 APPROX_EPSILON_RATIO = 0.04 ASPECT_RATIO_MIN = 0.5 ASPECT_RATIO_MAX = 2.0 # 排序矩形顶点(左上、右上、右下、左下) def sort_rect_vertices(vertices): if len(vertices) != 4: return vertices vertices.sort(key=lambda p: p[0] + p[1]) top_left, bottom_right = vertices[0], vertices[-1] mid = sorted(vertices[1:3], key=lambda p: p[0]) top_right, bottom_left = mid[1], mid[0] return [top_left, top_right, bottom_right, bottom_left] # 检测矩形轮廓 def detect_rectangle(contour): area = cv2.contourArea(contour) if not (MIN_AREA < area < MAX_AREA): return False, [] x, y, w, h = cv2.boundingRect(contour) if h == 0: return False, [] aspect_ratio = float(w) / h if not (ASPECT_RATIO_MIN < aspect_ratio < ASPECT_RATIO_MAX): return False, [] perimeter = cv2.arcLength(contour, True) if perimeter == 0: return False, [] epsilon = APPROX_EPSILON_RATIO * perimeter approx = cv2.approxPolyDP(contour, epsilon, True) if len(approx) != 4: return False, [] rect_vertices = [tuple(p[0]) for p in approx] sorted_vertices = sort_rect_vertices(rect_vertices) return True, sorted_vertices def main(): # 初始化摄像头和显示设备 cam = camera.Camera(320, 240) disp = display.Display() frame_count = 0 # 初始化串口 uart_dev = uart.list_devices() serial = uart.UART(uart_dev[0], 115200) if uart_dev else None while True: frame_count += 1 img = cam.read() img_cv = image.image2cv(img, copy=False) # 图像预处理 gray = cv2.cvtColor(img_cv, cv2.COLOR_BGR2GRAY) blurred = cv2.GaussianBlur(gray, (3, 3), 0) edged = cv2.Canny(blurred, CANNY_THRESH1, CANNY_THRESH2) edged = cv2.dilate(edged, KERNEL, 1) edged = cv2.erode(edged, KERNEL, 1) contours, _ = cv2.findContours(edged, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) contours = sorted(contours, key=cv2.contourArea, reverse=True)[:3] center_x, center_y, width = -1, -1, -1 for contour in contours: is_rect, vertices = detect_rectangle(contour) if is_rect and len(vertices) == 4: # 绘制矩形 cv2.polylines(img_cv, [np.array(vertices)], True, (0, 255, 0), 2) # 计算中心点 center_x = (vertices[0][0] + vertices[1][0] + vertices[2][0] + vertices[3][0]) // 4 center_y = (vertices[0][1] + vertices[1][1] + vertices[2][1] + vertices[3][1]) // 4 img.draw_circle(center_x, center_y, 3, image.COLOR_GREEN, -1) # 计算宽度 x_coords = [v[0] for v in vertices] width = max(x_coords) - min(x_coords) break # 每10帧发送一次数据,且仅当识别到矩形时才发送 if frame_count % 10 == 0 and serial: if center_x != -1 and center_y != -1 and width != -1: data_bytes = bytearray([ width & 0xFF, (width >> 8) & 0xFF, center_x & 0xFF, (center_x >> 8) & 0xFF, center_y & 0xFF, (center_y >> 8) & 0xFF ]) frame_data = bytearray(FRAME_HEADER) + data_bytes + bytearray(FRAME_TAIL) serial.write(bytes(frame_data)) print("发送数据:", list(frame_data)) else: print("未识别到图形,不发送数据") frame_count = 0 # 显示图像 img_show = image.cv2image(img_cv, copy=False) disp.show(img_show) if __name__ == "__main__": main() 提高识别精度,且不改变数据传输方式和格式

import cv2 import os def detect_windows(image_path): # 读取图像 image = cv2.imread(image_path) if image is None: print("无法读取图像,请检查路径是否正确。") return # 转换为灰度图像 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) gray = clahe.apply(gray) # 替换原有灰度转换 # 将原有高斯模糊替换为双边滤波 blurred = cv2.bilateralFilter(gray, 9, 75, 75) # 保留边缘同时降噪 # 使用Canny边缘检测 # 自动计算阈值(Otsu方法) high_thresh, _ = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) low_thresh = 0.5 * high_thresh edges = cv2.Canny(blurred, low_thresh, high_thresh) # 使用形态学操作增强边缘 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) dilated_edges = cv2.dilate(edges, kernel, iterations=1) closed_edges = cv2.morphologyEx(dilated_edges, cv2.MORPH_CLOSE, kernel) # 查找所有轮廓 contours, _ = cv2.findContours(closed_edges, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) # 初始化窗户列表 windows = [] # 遍历轮廓,找到所有窗户 for contour in contours: # 计算轮廓的边界矩形 x, y, w, h = cv2.boundingRect(contour) # 过滤条件:根据窗户的实际大小调整 if w > 5 and h > 5: # 允许非常小的窗户 # 计算轮廓的面积 area = cv2.contourArea(contour) # 过滤条件:根据轮廓的面积调整 if area > 50: # 允许非常小的窗户 windows.append((x, y, w, h)) # 按面积从小到大排序(优先处理小窗户) windows.sort(key=lambda rect: rect[2] * rect[3]) # 按面积排序 # 初始化最终窗户列表 final_windows = [] # 遍历窗户,排除重叠区域 for i, (x1, y1, w1, h1) in enumerate(windows): is_overlap = False # 检查是否与已选中的窗户重叠 for (x2, y2, w2, h2) in final_windows: if not (x1 + w1 < x2 or x1 > x2 + w2 or y1 + h1 < y2 or y1 > y2 + h2): is_overlap = True break # 如果没有重叠,则添加到最终窗户列表 if not is_overlap: final_windows.append((x1, y1, w1, h1)) # 初始化窗户计数器 window_count = 0 # 绘制最终窗户 for (x, y, w, h) in final_windows: cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 1) window_count += 1 # 输出窗户数量 print(f"图片 {image_path} 中窗户数量有 {window_count} 个。") # 检查并创建result文件夹 result_dir = "result" if not os.path.exists(result_dir): os.makedirs(result_dir) # 保存标记后的图像 result_path = os.path.join(result_dir, os.path.basename(image_path)) cv2.imwrite(result_path, image) # 显示结果图像 cv2.imshow("Detected Windows", image) cv2.waitKey(0) cv2.destroyAllWindows() # 示例调用 image_path = "C0001.jpg" # 替换为你的图片路径 detect_windows(image_path)有光噪声的地方识别不准

#! /usr/bin/env python3 # -*- coding: utf-8 -*- import cv2 import numpy as np import math # ------------------------------ # 1. 相机内参与畸变系数 # ------------------------------ # 原始K列表(一维) K_list = [247.04562, 0.0, 590.77428, 0.0, 246.15569, 645.21598, 0.0, 0.0, 1.0] K = np.array(K_list).reshape(3, 3).astype(np.float32) dist_list = [0.002208, -0.000261, -0.001007, -0.000014, 0.000000] dist = np.array(dist_list).astype(np.float32) # ------------------------------ # 2. 定义目标物三维特征点(世界坐标系) # ------------------------------ # A4纸边框4个角点(原点:A4纸中心M,单位:mm) A4_CORNERS_WORLD = np.array([ [-105.0, -148.5, 0.0], # 左下(x负,y负) [105.0, -148.5, 0.0], # 右下(x正,y负) [105.0, 148.5, 0.0], # 右上(x正,y正) [-105.0, 148.5, 0.0] # 左上(x负,y正) ], dtype=np.float32) # ------------------------------ # 3. 图像预处理与特征点提取 # ------------------------------ def extract_a4_corners(image): """提取A4纸边框的4个角点(像素坐标)和轮廓""" # 1. 畸变校正(必须先校正,否则角点坐标不准) undistorted = cv2.undistort(image, K, dist) # 2. 转灰度+增强对比度 gray = cv2.cvtColor(undistorted, cv2.COLOR_BGR2GRAY) gray = cv2.equalizeHist(gray) # 增强对比度,应对光照变化 # 3. 边缘检测(Canny) edges = cv2.Canny(gray, 80, 200) # 阈值可根据光照调整 # 4. 找轮廓(只保留外部轮廓) contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: return None, None # 5. 筛选A4纸轮廓(最大面积+矩形近似) max_contour = max(contours, key=cv2.contourArea) epsilon = 0.02 * cv2.arcLength(max_contour, True) approx = cv2.approxPolyDP(max_contour, epsilon, True) if len(approx) != 4: # 必须是四边形 return None, None # 6. 排序角点(左下→右下→右上→左上,对应世界坐标顺序) corners = approx.reshape(4, 2).astype(np.float32) # 按x+y之和排序(左下最小,右上最大) corners = sorted(corners, key=lambda p: p[0] + p[1]) # 调整顺序:左下→右下→右上→左上 bottom_left = corners[0] bottom_right = sorted(corners[1:3], key=lambda p: p[0])[1] top_right = corners[3] top_left = sorted(corners[1:3], key=lambda p: p[0])[0] sorted_corners = np.array([bottom_left, bottom_right, top_right, top_left], dtype=np.float32) return sorted_corners, max_contour def calculate_real_size_from_pixel(pixel_size, R, tvec): """根据像素尺寸计算真实世界尺寸""" # 使用Z方向距离(深度)进行计算 z_distance = abs(tvec[2][0]) # 深度距离(mm) # 使用平均焦距进行计算 focal_length_avg = (K[0][0] + K[1][1]) / 2 # 相机针孔模型:real_size = pixel_size * distance / focal_length real_size_mm = (pixel_size * z_distance) / focal_length_avg real_size_cm = real_size_mm / 10.0 # mm转换为cm return real_size_cm def detect_shapes_in_a4(image, a4_corners_pixel, R, tvec): """在A4纸区域内检测几何图形(圆形、等边三角形、正方形)""" # 1. 估算A4纸区域的中心点和尺寸 x_coords = a4_corners_pixel[:, 0] y_coords = a4_corners_pixel[:, 1] center_x = int(np.mean(x_coords)) center_y = int(np.mean(y_coords)) width = int(np.max(x_coords) - np.min(x_coords)) height = int(np.max(y_coords) - np.min(y_coords)) # 2. 提取A4纸中心区域(约为半尺寸) roi_size = min(width, height) // 3 x1 = max(0, center_x - roi_size // 2) y1 = max(0, center_y - roi_size // 2) x2 = min(image.shape[1], center_x + roi_size // 2) y2 = min(image.shape[0], center_y + roi_size // 2) roi = image[y1:y2, x1:x2] if roi.size == 0: return [], [] # 3. 图像预处理 gray_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY) # 使用自适应阈值二值化 binary_roi = cv2.adaptiveThreshold(gray_roi, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2) # 4. 找轮廓 contours, _ = cv2.findContours(binary_roi, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) detected_shapes = [] shape_measurements = [] for contour in contours: area = cv2.contourArea(contour) if area < 500: # 过滤太小的轮廓 continue # 计算轮廓的几何特征 perimeter = cv2.arcLength(contour, True) approx = cv2.approxPolyDP(contour, 0.02 * perimeter, True) # 计算圆形度(圆形度 = 4*pi*面积/周长^2,圆的圆形度接近1) circularity = 4 * np.pi * area / (perimeter ** 2) if perimeter > 0 else 0 # 调整轮廓坐标到原始图像坐标系 adjusted_contour = contour + np.array([x1, y1]) # 判断形状 if circularity > 0.7: # 圆形 # 计算外接圆 (circle_x, circle_y), radius = cv2.minEnclosingCircle(adjusted_contour) # 转换为真实世界坐标的直径 real_diameter = calculate_real_size_from_pixel(radius * 2, R, tvec) # 修正括号错误 detected_shapes.append(('circle', adjusted_contour, (int(circle_x), int(circle_y)), int(radius)) shape_measurements.append(('circle', real_diameter, f"Diameter: {real_diameter:.2f} cm")) elif len(approx) == 3: # 三角形 # 计算三角形的边长 triangle_points = approx.reshape(-1, 2) triangle_points += np.array([x1, y1]) side_lengths_pixel = [] for i in range(3): p1 = triangle_points[i] p2 = triangle_points[(i + 1) % 3] side_length_pixel = np.linalg.norm(p2 - p1) side_lengths_pixel.append(side_length_pixel) # 转换为真实世界坐标的边长 avg_side_pixel = np.mean(side_lengths_pixel) real_side_length = calculate_real_size_from_pixel(avg_side_pixel, R, tvec) detected_shapes.append(('triangle', adjusted_contour, triangle_points, None)) shape_measurements.append(('triangle', real_side_length, f"Side Length: {real_side_length:.2f} cm")) elif len(approx) == 4: # 四边形(正方形) # 检查是否为正方形 square_points = approx.reshape(-1, 2) square_points += np.array([x1, y1]) side_lengths_pixel = [] for i in range(4): p1 = square_points[i] p2 = square_points[(i + 1) % 4] side_length_pixel = np.linalg.norm(p2 - p1) side_lengths_pixel.append(side_length_pixel) # 检查边长是否相等(允许一定误差) avg_side = np.mean(side_lengths_pixel) if all(abs(side - avg_side) / avg_side < 0.2 for side in side_lengths_pixel): # 转换为真实世界坐标的边长 real_side_length = calculate_real_size_from_pixel(avg_side, R, tvec) detected_shapes.append(('square', adjusted_contour, square_points, None)) shape_measurements.append(('square', real_side_length, f"Side Length: {real_side_length:.2f} cm")) return detected_shapes, shape_measurements def calculate_pose_and_measurements(image, a4_corners_world): """ 输入:图像、A4纸三维角点 输出:距离、A4纸边长、旋转矩阵、角点、轮廓、位置、检测的形状 """ # 提取A4纸角点(像素坐标)和轮廓 a4_corners_pixel, contour = extract_a4_corners(image) if a4_corners_pixel is None: return None, None, None, None, None, None, None, None # 运行PNP算法 retval, rvec, tvec = cv2.solvePnP( a4_corners_world, a4_corners_pixel, K, dist, flags=cv2.SOLVEPNP_EPNP ) if not retval: print("PNP求解失败!") return None, None, None, None, None, None, None, None # 转换旋转向量为旋转矩阵(3x3) R, _ = cv2.Rodrigues(rvec) # 计算距离摄像头的位置(优化版) # tvec是A4纸中心在相机坐标系中的位置(mm) distance_x = tvec[0][0] / 10.0 # X方向偏移(cm) distance_y = tvec[1][0] / 10.0 # Y方向偏移(cm) distance_z = tvec[2][0] / 10.0 # Z方向距离(深度,cm) # 主要关注Z方向距离(摄像头到A4纸的直线距离) camera_to_a4_distance = abs(distance_z) # 直线距离 total_distance = math.sqrt(distance_x**2 + distance_y**2 + distance_z**2) # 空间距离 # 计算A4纸边长 # 注意:这里使用世界坐标点直接计算边长更简单 # 长边(210mm) long_side_world = np.linalg.norm(a4_corners_world[0] - a4_corners_world[1]) # 短边(297mm) short_side_world = np.linalg.norm(a4_corners_world[1] - a4_corners_world[2]) # 转换为cm long_side = long_side_world / 10.0 short_side = short_side_world / 10.0 # 检测A4纸中的几何图形 detected_shapes, shape_measurements = detect_shapes_in_a4(image, a4_corners_pixel, R, tvec) return (camera_to_a4_distance, total_distance), (long_side, short_side), R, a4_corners_pixel, contour, (distance_x, distance_y, distance_z), detected_shapes, shape_measurements if __name__ == "__main__": cap = cv2.VideoCapture(0) if not cap.isOpened(): print("无法打开摄像头") exit() while True: ret, frame = cap.read() if not ret: print("无法读取视频帧") break # 计算位姿和测量数据 result = calculate_pose_and_measurements(frame, A4_CORNERS_WORLD) if result[0] is not None: distances, side_lengths, R, corners, contour, position, detected_shapes, shape_measurements = result camera_to_a4_distance, total_distance = distances long_side, short_side = side_lengths distance_x, distance_y, distance_z = position # 绘制A4纸轮廓 if contour is not None: cv2.drawContours(frame, [contour], -1, (0, 255, 0), 2) # 绘制角点 if corners is not None: for i, corner in enumerate(corners): cv2.circle(frame, tuple(corner.astype(int)), 5, (255, 0, 0), -1) cv2.putText(frame, str(i), tuple(corner.astype(int) + 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1) # 绘制轮廓边框 if corners is not None: pts = corners.astype(int) cv2.polylines(frame, [pts], True, (0, 0, 255), 2) # 绘制检测到的几何图形 for shape_info in detected_shapes: shape_type, shape_contour, points, radius = shape_info if shape_type == 'circle': center = points # (circle_x, circle_y) cv2.circle(frame, center, radius, (255, 255, 0), 2) cv2.putText(frame, 'Circle', (center[0] - 30, center[1] - radius - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 0), 2) elif shape_type == 'triangle': cv2.drawContours(frame, [shape_contour], -1, (0, 255, 255), 2) center = tuple(np.mean(points, axis=0).astype(int)) cv2.putText(frame, 'Triangle', (center[0] - 30, center[1] - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 255), 2) elif shape_type == 'square': cv2.drawContours(frame, [shape_contour], -1, (255, 0, 255), 2) center = tuple(np.mean(points, axis=0).astype(int)) cv2.putText(frame, 'Square', (center[0] - 30, center[1] - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 255), 2) # 显示测量信息 y_offset = 30 # 显示主要距离 cv2.putText(frame, f"Camera to A4: {camera_to_a4_distance:.1f} cm", (10, y_offset), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) y_offset += 30 cv2.putText(frame, f"Total Distance: {total_distance:.1f} cm", (10, y_offset), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) y_offset += 30 # 显示A4尺寸 cv2.putText(frame, f"A4 Size: {long_side:.1f} x {short_side:.1f} cm", (10, y_offset), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 200, 255), 2) y_offset += 30 # 显示位置信息 cv2.putText(frame, f"Position: X:{distance_x:.1f}, Y:{distance_y:.1f}, Z:{distance_z:.1f} cm", (10, y_offset), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (200, 0, 255), 2) y_offset += 30 # 显示几何图形测量结果 for shape_info in shape_measurements: shape_type, measurement_value, measurement_text = shape_info cv2.putText(frame, measurement_text, (10, y_offset), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 0), 2) y_offset += 30 else: # 显示提示信息 cv2.putText(frame, "No A4 paper detected", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) cv2.imshow("Camera Calibration and Measurement", frame) key = cv2.waitKey(1) if key & 0xFF == ord('q'): break elif key & 0xFF == ord('c'): # 添加校准功能 print("执行校准...") cap.release() cv2.destroyAllWindows() 检查这串代码并修改错误的部分,将完整正确无误的代码告诉我

from maix import camera, display, image, uart, time import numpy as np import cv2 # --------------------------- # 配置参数(极简稳定版,按需调整) # --------------------------- CONFIG = { # 必配项(无串口则注释) "uart_enable": True, # 是否启用串口 "uart_device": "/dev/ttyS0", "uart_baudrate": 115200, # 相机(强制640x480 RGB,避免格式冲突) "camera_resolution": (640, 480), "camera_format": "RGB", # 与OpenCV兼容的格式 # 检测参数(放宽到极致,确保识别) "min_sheet_area": 1000, # 小A4也能检测 "threshold": 127, # 固定阈值(放弃自适应,避免波动) "blur": (3, 3), # 轻量模糊 "morph_kernel": (3, 3), # 轻量形态学 # 正方形判定(彻底宽松) "min_square_edges": 2, # 2条边即可 "min_right_angles": 1, # 1个直角即可 "length_tolerance": 0.3, # 边长差异30%内 "angle_tolerance": 20, # 角度差异±20° "min_line_mm": 20, # 边最短20mm "max_line_mm": 200, # 边最长200mm # 调试(全开,方便定位) "debug": True } class Smoother: """数据平滑(不变)""" def __init__(self, window=3): self.buffer = [] self.window = window def update(self, val): if val <= 0: return val self.buffer.append(val) if len(self.buffer) > self.window: self.buffer.pop(0) return np.mean(self.buffer) if self.buffer else val class A4Detector: """ 终极稳定版: 1. 强制相机格式兼容 2. 彻底宽松的正方形判定 3. 全流程异常捕获 4. 极简预处理(避免复杂操作崩溃) """ def __init__(self, config): self.cfg = config self.serial = self._init_uart() self.cam, self.disp = self._init_camera() self.fx = 1383.0 # 默认焦距,无标定也能跑 self.distance_smoother = Smoother() self.side_smoother = Smoother() def _init_uart(self): """串口初始化(容错)""" if not self.cfg["uart_enable"]: print("串口已禁用") return None try: return uart.UART(self.cfg["uart_device"], self.cfg["uart_baudrate"]) except Exception as e: print(f"串口失败: {e},已禁用串口功能") self.cfg["uart_enable"] = False return None def _init_camera(self): """强制相机格式为RGB,避免转换崩溃""" try: cam = camera.Camera( width=self.cfg["camera_resolution"][0], height=self.cfg["camera_resolution"][1], format=self.cfg["camera_format"] ) disp = display.Display() return cam, disp except Exception as e: print(f"相机初始化失败: {e},程序退出") raise SystemExit(1) def _preprocess(self, frame): """极简预处理:只做二值化,避免复杂操作""" try: # 转灰度(强制处理,不管格式) if frame.shape[2] == 3: # RGB gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY) else: # 单通道 gray = frame # 固定阈值二值化(放弃自适应,稳定) _, binary = cv2.threshold(gray, self.cfg["threshold"], 255, cv2.THRESH_BINARY_INV) # 轻量形态学 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, self.cfg["morph_kernel"]) binary = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel) return gray, binary except Exception as e: print(f"预处理崩溃: {e},跳过该帧") return None, None def _find_a4(self, binary): """找A4外框(只找最大轮廓,放弃四边形判定,优先稳定)""" try: contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: return None # 只取最大轮廓(放弃形状判定,优先稳定) return max(contours, key=cv2.contourArea) except Exception as e: print(f"A4检测崩溃: {e},跳过该帧") return None def _calculate_distance(self, a4_contour): """极简距离计算(无标定也能跑,假设宽度210mm)""" try: x, y, w, h = cv2.boundingRect(a4_contour) return (1383.0 * 210.0) / w # 硬编码焦距,放弃标定 except Exception as e: print(f"距离计算崩溃: {e},返回0") return 0.0 def _find_square_edges(self, gray, a4_contour): """暴力找所有边,再筛选正方形特征(彻底宽松)""" try: x, y, w, h = cv2.boundingRect(a4_contour) # 裁剪ROI(全A4,不偏移,避免漏检) roi = gray[y:y+h, x:x+w] _, roi_bin = cv2.threshold(roi, self.cfg["threshold"], 255, cv2.THRESH_BINARY_INV) # 找所有轮廓(包括嵌套、重叠) contours, _ = cv2.findContours(roi_bin, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) valid_edges = [] scale = 210.0 / w # 硬编码A4宽度,放弃标定 for cnt in contours: area = cv2.contourArea(cnt) if area < 50: # 过滤小噪声 continue # 轮廓逼近 perimeter = cv2.arcLength(cnt, True) approx = cv2.approxPolyDP(cnt, 0.02 * perimeter, True) # 宽松判定正方形 if self._is_square(approx, scale): # 提取所有边 self._extract_edges(approx, scale, valid_edges) return valid_edges except Exception as e: print(f"正方形检测崩溃: {e},返回空") return [] def _is_square(self, approx, scale): """彻底宽松的正方形判定""" if len(approx) < self.cfg["min_square_edges"]: return False edges = [] angles = [] for i in range(len(approx)): p1 = approx[i][0] p2 = approx[(i+1)%len(approx)][0] # 计算边长 length = np.linalg.norm(p1 - p2) * scale edges.append(length) # 计算角度 if i < len(approx)-1: p3 = approx[(i+2)%len(approx)][0] vec1 = p2 - p1 vec2 = p3 - p2 dot = np.dot(vec1, vec2) norm = np.linalg.norm(vec1) * np.linalg.norm(vec2) if norm == 0: continue angle = np.degrees(np.arccos(np.clip(dot/norm, -1, 1))) angles.append(angle) # 角度检查(至少1个直角) if sum(1 for a in angles if 90-self.cfg["angle_tolerance"]<a<90+self.cfg["angle_tolerance"]) < self.cfg["min_right_angles"]: return False # 边长检查(差异30%内) avg_len = np.mean(edges) if any(abs(l - avg_len)/avg_len > self.cfg["length_tolerance"] for l in edges): return False # 边长范围 if not (self.cfg["min_line_mm"] < avg_len < self.cfg["max_line_mm"]): return False return True def _extract_edges(self, approx, scale, edges_list): """提取轮廓的所有边""" for i in range(len(approx)): p1 = approx[i][0] p2 = approx[(i+1)%len(approx)][0] length = np.linalg.norm(p1 - p2) * scale edges_list.append( (p1, p2, length) ) def _find_shortest(self, edges): """找最短边(无有效边则返回0)""" if not edges: return 0.0, None edges.sort(key=lambda x: x[2]) return edges[0][2], (edges[0][0], edges[0][1]) def _visualize(self, frame, a4_cnt, distance, shortest_len, shortest_edge): """极简可视化(只画必要内容)""" try: vis = frame.copy() # 画A4框 if a4_cnt is not None: cv2.drawContours(vis, [a4_cnt], -1, (0,0,255), 2) cv2.putText(vis, f"A4: {distance:.1f}mm", (50,50), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0,0,255), 2) # 画最短边 if shortest_edge: p1, p2 = shortest_edge cv2.line(vis, (int(p1[0]), int(p1[1])), (int(p2[0]), int(p2[1])), (0,255,0), 3) mid = ( (p1[0]+p2[0])//2, (p1[1]+p2[1])//2 ) cv2.putText(vis, f"Short: {shortest_len:.1f}mm", mid, cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,255,0), 2) return vis except Exception as e: print(f"可视化崩溃: {e},返回原图") return frame def _send(self, distance, side): """串口发送(容错)""" if not self.cfg["uart_enable"] or not self.serial: return try: self.serial.write_str(f"D:{int(distance*10)},S:{int(side*10)}\n") except Exception as e: print(f"串口发送失败: {e}") def run(self): """主循环(全流程try-except,永不崩溃)""" try: while True: # 读帧(容错) img = self.cam.read() if img is None: time.sleep(0.01) continue frame = image.image2cv(img, copy=False) # 转OpenCV格式 # 预处理 gray, binary = self._preprocess(frame) if gray is None: continue # 找A4 a4_cnt = self._find_a4(binary) if a4_cnt is None: self.disp.show(img) continue # 计算距离 distance = self.distance_smoother.update(self._calculate_distance(a4_cnt)) # 找正方形边 edges = self._find_square_edges(gray, a4_cnt) shortest_len, shortest_edge = self._find_shortest(edges) shortest_len = self.side_smoother.update(shortest_len) # 可视化 vis = self._visualize(frame, a4_cnt, distance, shortest_len, shortest_edge) self.disp.show(image.cv2image(vis)) # 发送 self._send(distance, shortest_len) # 打印(调试) if self.cfg["debug"]: print(f"A4距离: {distance:.1f}mm | 最短边: {shortest_len:.1f}mm") time.sleep(0.01) except KeyboardInterrupt: print("手动退出") except Exception as e: print(f"致命错误: {e},程序重启建议") # 这里可加重启逻辑,或简单退出 raise SystemExit(1) if __name__ == "__main__": # 配置按需调整,比如关闭串口: # CONFIG["uart_enable"] = False detector = A4Detector(CONFIG) detector.run()该代码的作用是要识别到的是6cm~12cm中的最短边,但该最短边一定得是一个正方形的完整边(正方形可能是完整也可能是有部分重叠的),不满足这个条件的最短边直接舍去,现在该代码识别不稳,有时识别最长边,有时识别最短边,帮我优化代码,让其稳定识别出满足要求的最短边

import numpy as np import cv2 from scipy.spatial import KDTree from scipy.optimize import least_squares from sklearn.decomposition import PCA from sklearn.cluster import KMeans from sklearn.neighbors import NearestNeighbors import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D import os import time class GranularBallSFM: def __init__(self, params=None): # 算法参数(基于论文中的理论推导和实验验证) self.params = params or { # 特征预处理参数 'sift_contrast_threshold': 0.04, 'superpoint_threshold': 0.005, 'entropy_window_size': 15, # 粒度球生成参数 'init_radius': 100, 'min_radius': 15, 'max_radius': 50, 'eta': 0.8, # 梯度-半径转换系数 'kappa': 20, # 熵项缩放系数 'tau': 5, # 熵项温度参数 'topology_tau': 5, # 拓扑容差参数 # 粗粒度重建参数 'coarse_threshold': 50, 'subblock_size': 8, # 细粒度重建参数 'fine_threshold': 15, 'huber_delta': 2.0, # 低纹理补足参数 'textureless_entropy': 2.0, 'textureless_radius': 100, 'icp_lambda': 0.1, # 可视化参数 'visualize': True } # 存储中间结果 self.images = [] self.keypoints = [] self.descriptors = [] self.entropy_maps = [] self.gradient_maps = [] self.granular_balls = [] self.topology_graph = None self.camera_poses = [] self.point_cloud = [] self.reconstructed_mesh = None def load_images(self, image_paths): """加载图像序列""" print(f"加载 {len(image_paths)} 张图像...") self.images = [cv2.imread(path) for path in image_paths] self.images = [cv2.cvtColor(img, cv2.COLOR_BGR2RGB) for img in self.images] self.images = [cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) for img in self.images] return self.images def feature_preprocessing(self): """特征预处理(4.1节)""" print("执行特征预处理...") sift = cv2.SIFT_create(contrastThreshold=self.params['sift_contrast_threshold']) self.keypoints = [] self.descriptors = [] self.entropy_maps = [] self.gradient_maps = [] for img in self.images: # 1. 计算SIFT特征 kp, des = sift.detectAndCompute(img, None) # 2. 计算局部熵图 entropy_map = self._compute_entropy_map(img) # 3. 计算梯度幅值图 gradient_map = self._compute_gradient_map(img) self.keypoints.append(kp) self.descriptors.append(des) self.entropy_maps.append(entropy_map) self.gradient_maps.append(gradient_map) return self.keypoints, self.descriptors, self.entropy_maps, self.gradient_maps def _compute_entropy_map(self, img): """计算局部熵图(公式4)""" h, w = img.shape entropy_map = np.zeros((h, w)) win_size = self.params['entropy_window_size'] half_win = win_size // 2 for y in range(half_win, h - half_win): for x in range(half_win, w - half_win): window = img[y - half_win:y + half_win + 1, x - half_win:x + half_win + 1] hist = np.histogram(window, bins=256, range=(0, 255))[0] hist = hist / hist.sum() + 1e-10 # 避免除以零 entropy = -np.sum(hist * np.log2(hist)) entropy_map[y, x] = entropy # 归一化 entropy_map = (entropy_map - entropy_map.min()) / (entropy_map.max() - entropy_map.min()) return entropy_map def _compute_gradient_map(self, img): """计算梯度幅值图(公式3)""" sobel_x = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3) sobel_y = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3) gradient_magnitude = np.sqrt(sobel_x ** 2 + sobel_y ** 2) # 归一化 gradient_magnitude = (gradient_magnitude - gradient_magnitude.min()) / \ (gradient_magnitude.max() - gradient_magnitude.min()) return gradient_magnitude def generate_granular_balls(self, img_idx=0): """粒度球生成算法(4.2节)""" print(f"为图像 {img_idx} 生成粒度球...") img = self.images[img_idx] entropy_map = self.entropy_maps[img_idx] gradient_map = self.gradient_maps[img_idx] kps = self.keypoints[img_idx] h, w = img.shape init_radius = self.params['init_radius'] min_radius = self.params['min_radius'] max_radius = self.params['max_radius'] eta = self.params['eta'] kappa = self.params['kappa'] tau = self.params['tau'] # 初始化粗粒度粒球 balls = [] queue = [] # 创建初始网格覆盖 for y in range(init_radius // 2, h, init_radius): for x in range(init_radius // 2, w, init_radius): center = (x, y) radius = init_radius projection = self._get_projection_area(img, center, radius) ball = {'center': center, 'radius': radius, 'projection': projection} queue.append(ball) # 自适应粒度分裂 balls_fine = [] while queue: ball = queue.pop(0) center = ball['center'] radius = ball['radius'] projection = ball['projection'] # 计算平均梯度(公式3) G_avg = np.mean([gradient_map[p[1], p[0]] for p in projection]) # 计算局部熵(公式4) hist = np.histogram([img[p[1], p[0]] for p in projection], bins=256, range=(0, 255))[0] hist = hist / hist.sum() + 1e-10 E_local = -np.sum(hist * np.log2(hist)) # 计算自适应半径(公式2) r_k = eta * G_avg + kappa * np.exp(-E_local / tau) # 粒度决策 if r_k < min_radius: # 细粒度区域 - 分裂 sub_balls = self._split_ball(ball, r_k, img) for sub_ball in sub_balls: if sub_ball['radius'] > max_radius: queue.append(sub_ball) else: balls_fine.append(sub_ball) elif r_k > max_radius: # 需要重新分裂 ball['radius'] = r_k queue.append(ball) else: # 最优粒度 - 保留 ball['radius'] = r_k balls.append(ball) balls += balls_fine self.granular_balls = balls # 构建拓扑图(公式8) self.topology_graph = self._build_topology_graph(balls) return balls, self.topology_graph def _get_projection_area(self, img, center, radius): """获取粒球投影区域""" h, w = img.shape xc, yc = center projection = [] for y in range(max(0, int(yc - radius)), min(h, int(yc + radius + 1))): for x in range(max(0, int(xc - radius)), min(w, int(xc + radius + 1))): if (x - xc) ** 2 + (y - yc) ** 2 <= radius ** 2: projection.append((x, y)) return projection def _split_ball(self, ball, target_radius, img): """分裂粒球""" center = ball['center'] radius = ball['radius'] projection = ball['projection'] # 提取粒球内特征点 points = [] for p in projection: points.append(p) if len(points) < 4: # 最少需要4个点进行聚类 return [ball] # K-means聚类 - 修复了括号问题 k = max(2, int(np.sqrt(len(points))) // 2 kmeans = KMeans(n_clusters=k, random_state=0).fit(points) labels = kmeans.labels_ centers = kmeans.cluster_centers_ # 创建子粒球 sub_balls = [] for i in range(k): cluster_points = [points[j] for j in range(len(points)) if labels[j] == i] if not cluster_points: continue # 计算新球心和新半径 center_new = (int(centers[i][0]), int(centers[i][1])) distances = [np.linalg.norm(np.array(p) - np.array(center_new)) for p in cluster_points] radius_new = min(target_radius, np.max(distances) if distances else target_radius) # 确保半径不小于最小值 radius_new = max(radius_new, self.params['min_radius']) projection_new = self._get_projection_area(img, center_new, radius_new) sub_ball = {'center': center_new, 'radius': radius_new, 'projection': projection_new} sub_balls.append(sub_ball) return sub_balls def _build_topology_graph(self, balls): """构建粒球拓扑图(公式8)""" print("构建拓扑图...") centers = [ball['center'] for ball in balls] radii = [ball['radius'] for ball in balls] tau = self.params['topology_tau'] # 使用KDTree加速邻域搜索 kdtree = KDTree(centers) graph = {i: [] for i in range(len(balls))} for i, ball in enumerate(balls): # 查找邻近粒球 neighbors = kdtree.query_ball_point(ball['center'], ball['radius'] + max(radii) + tau) for j in neighbors: if i == j: continue # 计算中心距离 dist = np.linalg.norm(np.array(ball['center']) - np.array(balls[j]['center'])) # 检查是否相交(公式8) if dist <= ball['radius'] + balls[j]['radius'] + tau: graph[i].append(j) return graph def hierarchical_reconstruction(self): """分层三维重建(4.3节)""" print("开始分层三维重建...") # 阶段1: 粗粒度全局骨架构建 self.coarse_global_reconstruction() # 阶段2: 细粒度局部BA优化 self.fine_grained_ba() # 阶段3: 低纹理区域补足 self.textureless_completion() print("三维重建完成!") return self.point_cloud, self.camera_poses, self.reconstructed_mesh def coarse_global_reconstruction(self): """粗粒度全局骨架构建(4.3.1节)""" print("粗粒度全局骨架构建...") coarse_balls = [ball for ball in self.granular_balls if ball['radius'] > self.params['coarse_threshold']] # 1. 计算EWGD描述子(公式14-16) for ball in coarse_balls: # 获取粒球内的特征点 features = self._get_features_in_ball(ball) if not features: continue # 计算SIFT聚合特征(公式14) sift_features = [f[2] for f in features if f[2] is not None] if sift_features: sift_agg = np.mean(sift_features, axis=0)[:64] # 64维均值向量 else: sift_agg = np.zeros(64) # 计算熵权子块特征(公式15) subblock_feature = self._compute_entropy_weighted_subblocks(ball) # 拼接EWGD描述子(公式16) ewgd = np.concatenate((sift_agg, subblock_feature)) ball['ewgd'] = ewgd # 估计法向量(PCA) points = [f[:2] for f in features] if len(points) >= 3: pca = PCA(n_components=3) pca.fit(points) ball['normal'] = pca.components_[2] # 最小特征值对应的特征向量 else: ball['normal'] = np.array([0, 0, 1]) # 2. 全局旋转优化(公式17) self._global_rotation_optimization(coarse_balls) # 初始化相机位姿 self.camera_poses = [np.eye(4) for _ in range(len(self.images))] # 初始化点云 self.point_cloud = self._initialize_point_cloud(coarse_balls) def _get_features_in_ball(self, ball): """获取粒球内的特征点""" features = [] center = ball['center'] radius = ball['radius'] for i, kp in enumerate(self.keypoints[0]): # 假设使用第一张图像的特征点 x, y = kp.pt if (x - center[0]) ** 2 + (y - center[1]) ** 2 <= radius ** 2: desc = self.descriptors[0][i] if self.descriptors[0] is not None else None features.append((x, y, desc)) return features def _compute_entropy_weighted_subblocks(self, ball): """计算熵权子块特征(公式15)""" center = ball['center'] radius = ball['radius'] img = self.images[0] entropy_map = self.entropy_maps[0] subblock_size = self.params['subblock_size'] # 划分粒球为子块 x_min = max(0, int(center[0] - radius)) x_max = min(img.shape[1], int(center[0] + radius)) y_min = max(0, int(center[1] - radius)) y_max = min(img.shape[0], int(center[1] + radius)) subblock_width = (x_max - x_min) // subblock_size subblock_height = (y_max - y_min) // subblock_size subblock_feature = np.zeros(128) # 简化版本 # 简化实现:实际应使用更复杂的特征提取 for i in range(subblock_size): for j in range(subblock_size): # 计算子块位置 x_start = x_min + i * subblock_width x_end = x_min + (i + 1) * subblock_width y_start = y_min + j * subblock_height y_end = y_min + (j + 1) * subblock_height if x_start >= x_end or y_start >= y_end: continue # 计算子块熵值 sub_entropy = np.mean(entropy_map[y_start:y_end, x_start:x_end]) # 计算权重(熵越高权重越大) weight = np.exp(sub_entropy) # 简化权重计算 # 提取子块特征(简化实现) sub_img = img[y_start:y_end, x_start:x_end] sub_feature = np.histogram(sub_img.flatten(), bins=128, range=(0, 255))[0] sub_feature = sub_feature / sub_feature.sum() if sub_feature.sum() > 0 else sub_feature # 加权累加 subblock_feature += weight * sub_feature return subblock_feature def _global_rotation_optimization(self, coarse_balls): """全局旋转优化(公式17)""" # 简化实现:实际应使用李代数优化 print("执行全局旋转优化...") # 构建法向量图 normals = [ball.get('normal', np.array([0, 0, 1])) for ball in coarse_balls] # 优化目标:最小化相邻粒球法向量差异 # 实际实现应使用更复杂的优化算法 for i, ball in enumerate(coarse_balls): if i not in self.topology_graph: continue neighbors = self.topology_graph[i] if not neighbors: continue # 计算平均法向量 neighbor_normals = [coarse_balls[j].get('normal', np.array([0, 0, 1])) for j in neighbors] avg_normal = np.mean(neighbor_normals, axis=0) avg_normal = avg_normal / np.linalg.norm(avg_normal) # 更新法向量 ball['normal'] = 0.7 * ball['normal'] + 0.3 * avg_normal ball['normal'] = ball['normal'] / np.linalg.norm(ball['normal']) def _initialize_point_cloud(self, coarse_balls): """初始化点云""" point_cloud = [] for ball in coarse_balls: # 简化实现:使用球心作为3D点 # 实际实现应使用三角测量 x, y = ball['center'] z = 0 # 简化假设 normal = ball.get('normal', np.array([0, 0, 1])) point_cloud.append((x, y, z, normal)) return point_cloud def fine_grained_ba(self): """细粒度BA优化(4.3.2节)""" print("细粒度BA优化...") fine_balls = [ball for ball in self.granular_balls if ball['radius'] < self.params['fine_threshold']] # 简化实现:实际应使用LM优化 for ball in fine_balls: # 获取粒球内的3D点 points_in_ball = self._get_points_in_ball(ball) if not points_in_ball: continue # 获取观测到这些点的相机 cameras = self._get_cameras_observing_points(points_in_ball) # 优化残差(公式18) residuals = [] for point_idx in points_in_ball: for cam_idx in cameras: # 计算重投影误差 error = self._compute_reprojection_error(point_idx, cam_idx) # 应用Huber损失(公式19) huber_error = self._huber_loss(error, self.params['huber_delta']) residuals.append(huber_error) # 平均残差(简化) avg_residual = np.mean(residuals) if residuals else 0 # 更新点位置(简化实现) # 实际实现应使用优化算法更新点和相机参数 for point_idx in points_in_ball: self._update_point_position(point_idx, avg_residual) # 更新亏格(公式12-13) self._update_genus() def _get_points_in_ball(self, ball): """获取粒球内的3D点""" points_in_ball = [] center = ball['center'] radius = ball['radius'] for i, point in enumerate(self.point_cloud): x, y, z, _ = point if (x - center[0]) ** 2 + (y - center[1]) ** 2 <= radius ** 2: points_in_ball.append(i) return points_in_ball def _get_cameras_observing_points(self, point_indices): """获取观测到指定点的相机""" # 简化实现:返回所有相机 return list(range(len(self.camera_poses))) def _compute_reprojection_error(self, point_idx, cam_idx): """计算重投影误差""" # 简化实现 return np.random.rand() * 2 # 随机误差 def _huber_loss(self, r, delta): """Huber损失函数(公式19)""" if abs(r) <= delta: return 0.5 * r ** 2 else: return delta * (abs(r) - 0.5 * delta) def _update_point_position(self, point_idx, residual): """更新点位置(简化)""" # 实际实现应使用优化算法 x, y, z, normal = self.point_cloud[point_idx] # 沿法线方向微调 self.point_cloud[point_idx] = (x, y, z + residual * 0.01, normal) def _update_genus(self): """更新亏格(公式12-13)""" # 简化实现 pass def textureless_completion(self): """低纹理区域补足(4.3.3节)""" print("低纹理区域补足...") textureless_balls = [] # 识别低纹理区域(公式21) for ball in self.granular_balls: # 计算局部熵 features = self._get_features_in_ball(ball) if not features: continue intensities = [self.images[0][int(p[1]), int(p[0])] for p in features] hist = np.histogram(intensities, bins=256, range=(0, 255))[0] hist = hist / hist.sum() + 1e-10 E_local = -np.sum(hist * np.log2(hist)) # 检查触发条件 if E_local < self.params['textureless_entropy'] and \ ball['radius'] > self.params['textureless_radius']: textureless_balls.append(ball) # 执行点面ICP(公式22) for ball in textureless_balls: self._point_to_plane_icp(ball) # 孔洞填充 self._fill_holes() def _point_to_plane_icp(self, ball): """点面ICP优化(公式22)""" # 简化实现 center = np.array(ball['center']) normal = ball.get('normal', np.array([0, 0, 1])) # 查找邻近点 points = np.array([p[:3] for p in self.point_cloud]) if len(points) == 0: return nn = NearestNeighbors(n_neighbors=10).fit(points) distances, indices = nn.kneighbors([center]) # 计算参考平面 neighbor_points = points[indices[0]] pca = PCA(n_components=3) pca.fit(neighbor_points) ref_normal = pca.components_[2] ref_point = np.mean(neighbor_points, axis=0) # 更新法向量 ball['normal'] = 0.5 * normal + 0.5 * ref_normal ball['normal'] = ball['normal'] / np.linalg.norm(ball['normal']) def _fill_holes(self): """孔洞填充""" # 简化实现 print("执行孔洞填充...") # 实际实现应使用Poisson表面重建等算法 self.reconstructed_mesh = self.point_cloud def visualize_results(self): """可视化重建结果""" if not self.params['visualize']: return fig = plt.figure(figsize=(15, 10)) # 1. 可视化粒球 ax1 = fig.add_subplot(221) img_idx = 0 img = self.images[img_idx] ax1.imshow(img, cmap='gray') ax1.set_title('Granular Balls') for ball in self.granular_balls: center = ball['center'] radius = ball['radius'] circle = plt.Circle(center, radius, color='r', fill=False, alpha=0.5) ax1.add_patch(circle) # 标记粗粒度球 if ball['radius'] > self.params['coarse_threshold']: ax1.plot(center[0], center[1], 'bo', markersize=3) # 标记细粒度球 elif ball['radius'] < self.params['fine_threshold']: ax1.plot(center[0], center[1], 'go', markersize=3) # 2. 可视化拓扑连接 ax2 = fig.add_subplot(222) ax2.imshow(img, cmap='gray') ax2.set_title('Topology Graph') for i, ball in enumerate(self.granular_balls): if i in self.topology_graph: neighbors = self.topology_graph[i] for j in neighbors: if j > i: # 避免重复绘制 start = ball['center'] end = self.granular_balls[j]['center'] ax2.plot([start[0], end[0]], [start[1], end[1]], 'b-', alpha=0.3) # 3. 可视化点云 if self.point_cloud: ax3 = fig.add_subplot(223, projection='3d') ax3.set_title('3D Point Cloud') points = np.array([p[:3] for p in self.point_cloud]) normals = np.array([p[3] for p in self.point_cloud]) ax3.scatter(points[:, 0], points[:, 1], points[:, 2], c='b', s=1) # 可视化法向量 for i in range(0, len(points), 10): # 每隔10个点显示法向量 start = points[i] end = start + 10 * normals[i] ax3.plot([start[0], end[0]], [start[1], end[1]], [start[2], end[2]], 'r-') # 4. 可视化重建网格(简化) ax4 = fig.add_subplot(224, projection='3d') ax4.set_title('Reconstructed Surface') if self.point_cloud: points = np.array([p[:3] for p in self.point_cloud]) ax4.plot_trisurf(points[:, 0], points[:, 1], points[:, 2], cmap='viridis', alpha=0.8) plt.tight_layout() plt.savefig('granular_ball_sfm_results.png') plt.show() def run_full_pipeline(self, image_paths): """运行完整的三维重建流程""" start_time = time.time() # 1. 加载图像 self.load_images(image_paths) # 2. 特征预处理 self.feature_preprocessing() # 3. 粒度球生成(使用第一张图像) self.generate_granular_balls(img_idx=0) # 4. 分层三维重建 self.hierarchical_reconstruction() # 5. 可视化结果 self.visualize_results() end_time = time.time() print(f"完整流程耗时: {end_time - start_time:.2f}秒") return self.point_cloud, self.camera_poses, self.reconstructed_mesh # 使用示例 if __name__ == "__main__": # 图像路径(替换为您的图像序列) image_dir = "path/to/your/images" image_paths = [os.path.join(image_dir, f) for f in sorted(os.listdir(image_dir)) if f.endswith(('.jpg', '.png', '.jpeg'))] # 创建并运行算法 sfm = GranularBallSFM() point_cloud, camera_poses, reconstructed_mesh = sfm.run_full_pipeline(image_paths[:3]) # 使用前3张图像测试 # 保存结果(根据实际需要扩展) np.save("point_cloud.npy", np.array(point_cloud)) np.save("camera_poses.npy", np.array(camera_poses)) print("三维重建结果已保存!")"D:\Python Charm\Python Pro\pythonProject1\.venv\Scripts\python.exe" C:\Users\英杰\Desktop\777\代码\最终算法\pythonProject1\算法大框架.py File "C:\Users\英杰\Desktop\777\代码\最终算法\pythonProject1\算法大框架.py", line 228 k = max(2, int(np.sqrt(len(points))) // 2 ^ SyntaxError: '(' was never closed 进程已结束,退出代码为 1

import cv2 as cv import numpy as np # 读取目标图像 im1 = cv.imread("1.jpg") im1_gray = cv.cvtColor(im1, cv.COLOR_BGR2GRAY) # 读取待查找图像 im2 = cv.imread("2.jpg") im2_gray = cv.cvtColor(im2, cv.COLOR_BGR2GRAY) # 实例化一个orb检测器,opencv-contrib-python, opencv-python orb = cv.SIFT.create() # 完成目标图像、待查找图像 关键点检测,获取特征向量 kp1, des1 = orb.detectAndCompute(im1_gray, mask=None) kp2, des2 = orb.detectAndCompute(im2_gray, mask=None) # 第一次匹配,使用暴力匹配器,对匹配结果,按照距离进行排序,完成第一次筛选 FLANN_INDEX_KDTREE = 1 index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5) search_params = dict(checks=50) flann = cv.FlannBasedMatcher(index_params, search_params) match = flann.knnMatch(des1, des2, k=2) # 最近邻匹配,每一个元素存在两个匹配关系,一个是距离最小的,一个是距离次小的 good_match = [] for m in match: if m[0].distance < m[1].distance * 0.75: # 一个好的撇皮,最小距离应该小于(阈值*次小距离) good_match.append(m[0]) im_match2 = cv.drawMatches(im1, kp1, im2, kp2, good_match, None, cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS) # 根据第一次匹配的结果,将存在对应关系的关键点提取出来 p1 = [] p2 = [] for m in good_match: p1.append(kp1[m.queryIdx].pt) p2.append(kp2[m.trainIdx].pt) p1 = np.array(p1) p2 = np.array(p2) # 对p1,p2进行第二次筛选 H, inlines = cv.findHomography(p1, p2, method=cv.RANSAC, ransacReprojThreshold=3) index = np.where(inlines.ravel() == 1) final_match = [] for i in index[0]: final_match.append(good_match[i]) im_match3 = cv.drawMatches(im1, kp1, im2, kp2, final_match, None, cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS) cv.imshow("2", im_match2) cv.imshow("3", im_match3) cv.waitKey() print("图1变为图2 的透视矩阵为:", H) # im2根据变化矩阵完成映射,dsize需要设置为(图2宽+图1宽, 图1高 im2_change = cv.warpPerspective(im2, M=H, dsize=(im2.shape[1] + im1.shape[1], im1.shape[0])) # 对映射后图像进行切片,使得图像的左半部分是图1 im2_change[0:im1.shape[0], 0:im1.shape[1]] = im1 # 显示最终查找结果 cv.imshow("result", im2_change) cv.waitKey()

#include "ucar/ucar.hpp" #include <tf2/LinearMath/Quaternion.h> #include <ros/console.h> FileTransfer::FileTransfer(const std::string& baseDir ) : m_baseDir(baseDir), m_running(false), m_serverThread() { createDirectory(m_baseDir); } FileTransfer::~FileTransfer() { stopServer(); } // 功能1:保存字符串到本地文件 bool FileTransfer::saveToFile(const std::string& content, const std::string& filename) { std::string fullPath = m_baseDir + "/" + filename; std::ofstream file(fullPath); if (!file.is_open()) { std::cerr << "无法打开文件: " << fullPath << std::endl; return false; } file << content; file.close(); std::cout << "文件已保存到: " << fullPath << std::endl; return true; } // 功能2:发送文件到指定IP bool FileTransfer::sendTo(const std::string& serverIP, int port, const std::string& localFile, const std::string& remotePath ) { // 1. 读取文件内容 std::ifstream file(localFile, std::ios::binary | std::ios::ate); if (!file.is_open()) { std::cerr << "无法打开本地文件: " << localFile << std::endl; return false; } size_t fileSize = file.tellg(); file.seekg(0, std::ios::beg); std::vector<char> buffer(fileSize); if (!file.read(buffer.data(), fileSize)) { std::cerr << "读取文件错误: " << localFile << std::endl; return false; } file.close(); // 2. 创建Socket int sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { std::cerr << "创建Socket失败: " << strerror(errno) << std::endl; return false; } // 3. 连接服务器 sockaddr_in serverAddr; memset(&serverAddr, 0, sizeof(serverAddr)); serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(port); if (inet_pton(AF_INET, serverIP.c_str(), &serverAddr.sin_addr) <= 0) { std::cerr << "无效的IP地址: " << serverIP << std::endl; close(sock); return false; } if (connect(sock, (sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) { std::cerr << "连接服务器失败: " << strerror(errno) << std::endl; close(sock); return false; } // 4. 发送文件信息 if (!sendData(sock, remotePath, buffer.data(), fileSize)) { close(sock); return false; } close(sock); std::cout << "文件已发送到 " << serverIP << ":" << port << " 保存为 " << remotePath << std::endl; return true; } // 功能3:阻塞等待接收文件并读取内容 std::string FileTransfer::receiveAndRead(int port, int timeoutMs ) { // 确保服务器正在运行 if (!m_running) { startServer(port); } // 等待文件到达 ReceivedFile file; if (!waitForFile(file, timeoutMs)) { throw std::runtime_error("等待文件超时或服务器停止"); } stopServer(); // 返回文件内容 return std::string(file.content.data(), file.content.size()); } // 启动服务器(后台线程) void FileTransfer::startServer(int port) { if (m_running) { stopServer(); } m_running = true; m_serverThread = std::thread(&FileTransfer::serverThreadFunc, this, port); } // 停止服务器 void FileTransfer::stopServer() { if (m_running) { m_running = false; if (m_serverThread.joinable()) { m_serverThread.join(); } } } // 创建目录 bool FileTransfer::createDirectory(const std::string& path) { if (mkdir(path.c_str(), 0777) == -1 && errno != EEXIST) { std::cerr << "无法创建目录: " << path << " - " << strerror(errno) << std::endl; return false; } return true; } // 发送数据到socket bool FileTransfer::sendData(int sock, const std::string& remotePath, const char* data, size_t size) { // 1. 发送路径长度和路径 uint32_t pathLen = htonl(static_cast<uint32_t>(remotePath.size())); if (send(sock, &pathLen, sizeof(pathLen), 0) != sizeof(pathLen)) { std::cerr << "发送路径长度失败" << std::endl; return false; } if (send(sock, remotePath.c_str(), remotePath.size(), 0) != static_cast<ssize_t>(remotePath.size())) { std::cerr << "发送路径失败" << std::endl; return false; } // 2. 发送文件长度和内容 uint32_t netSize = htonl(static_cast<uint32_t>(size)); if (send(sock, &netSize, sizeof(netSize), 0) != sizeof(netSize)) { std::cerr << "发送文件长度失败" << std::endl; return false; } ssize_t totalSent = 0; while (totalSent < static_cast<ssize_t>(size)) { ssize_t sent = send(sock, data + totalSent, size - totalSent, 0); if (sent < 0) { std::cerr << "发送文件内容失败: " << strerror(errno) << std::endl; return false; } totalSent += sent; } return true; } // 服务器线程函数 void FileTransfer::serverThreadFunc(int port) { // 1. 创建Socket int listenSock = socket(AF_INET, SOCK_STREAM, 0); if (listenSock < 0) { std::cerr << "创建Socket失败: " << strerror(errno) << std::endl; return; } // 2. 设置SO_REUSEADDR int opt = 1; setsockopt(listenSock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); // 3. 绑定端口 sockaddr_in serverAddr; memset(&serverAddr, 0, sizeof(serverAddr)); serverAddr.sin_family = AF_INET; serverAddr.sin_addr.s_addr = INADDR_ANY; serverAddr.sin_port = htons(port); if (bind(listenSock, (sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) { std::cerr << "绑定端口失败: " << strerror(errno) << std::endl; close(listenSock); return; } // 4. 开始监听 if (listen(listenSock, 5) < 0) { std::cerr << "监听失败: " << strerror(errno) << std::endl; close(listenSock); return; } std::cout << "文件接收服务器启动,监听端口: " << port << std::endl; // 5. 接受连接循环 bool receivedFile = false; while (m_running&& !receivedFile) { // 设置超时以定期检查停止条件 fd_set readSet; FD_ZERO(&readSet); FD_SET(listenSock, &readSet); timeval timeout{0, 100000}; // 100ms int ready = select(listenSock + 1, &readSet, nullptr, nullptr, &timeout); if (ready < 0) { if (errno != EINTR) { std::cerr << "select错误: " << strerror(errno) << std::endl; } continue; } if (ready == 0) continue; // 超时,继续循环 // 6. 接受客户端连接 sockaddr_in clientAddr; socklen_t clientLen = sizeof(clientAddr); int clientSock = accept(listenSock, (sockaddr*)&clientAddr, &clientLen); if (clientSock < 0) { if (errno != EAGAIN && errno != EWOULDBLOCK) { std::cerr << "接受连接失败: " << strerror(errno) << std::endl; } continue; } char clientIP[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &clientAddr.sin_addr, clientIP, INET_ADDRSTRLEN); std::cout << "收到来自 " << clientIP << " 的文件传输请求" << std::endl; // 7. 接收文件 ReceivedFile file; if (receiveFile(clientSock, file)) { std::lock_guard<std::mutex> lock(m_mutex); m_receivedFiles.push(std::move(file)); receivedFile = true; m_cv.notify_one(); // 通知等待线程 } close(clientSock); } close(listenSock); std::cout << "文件接收服务器已停止" << std::endl; } // 接收文件 bool FileTransfer::receiveFile(int sock, ReceivedFile& file) { // 1. 接收路径长度和路径 uint32_t pathLen; if (recv(sock, &pathLen, sizeof(pathLen), 0) != sizeof(pathLen)) { std::cerr << "接收路径长度失败" << std::endl; return false; } pathLen = ntohl(pathLen); std::vector<char> pathBuffer(pathLen + 1); ssize_t received = recv(sock, pathBuffer.data(), pathLen, 0); if (received != static_cast<ssize_t>(pathLen)) { std::cerr << "接收路径失败" << std::endl; return false; } pathBuffer[pathLen] = '\0'; file.filename = pathBuffer.data(); // 2. 接收文件长度 uint32_t fileSize; if (recv(sock, &fileSize, sizeof(fileSize), 0) != sizeof(fileSize)) { std::cerr << "接收文件长度失败" << std::endl; return false; } fileSize = ntohl(fileSize); // 3. 接收文件内容 file.content.resize(fileSize); ssize_t totalReceived = 0; while (totalReceived < static_cast<ssize_t>(fileSize) && m_running) { ssize_t bytes = recv(sock, file.content.data() + totalReceived, fileSize - totalReceived, 0); if (bytes <= 0) { if (bytes < 0) { std::cerr << "接收文件内容失败: " << strerror(errno) << std::endl; } return false; } totalReceived += bytes; } std::cout << "成功接收文件: " << file.filename << " (" << fileSize << " 字节)" << std::endl; return true; } // 等待文件到达 bool FileTransfer::waitForFile(ReceivedFile& file, int timeoutMs) { std::unique_lock<std::mutex> lock(m_mutex); // 只要有文件就立即返回 auto pred = [&] { return !m_receivedFiles.empty(); // 只需检查队列是否非空 }; if (timeoutMs > 0) { if (!m_cv.wait_for(lock, std::chrono::milliseconds(timeoutMs), pred)) { return false; // 超时 } } else { m_cv.wait(lock, pred); } if (m_receivedFiles.empty()) return false; file = std::move(m_receivedFiles.front()); m_receivedFiles.pop(); return true; } // 接收文件并读取两行内容 bool FileTransfer::receiveAndReadTwoStrings(int port, std::string& outStr1, std::string& outStr2, int timeoutMs) { // 确保服务器正在运行 if (!m_running) { startServer(port); } // 等待文件到达 ReceivedFile file; if (!waitForFile(file, timeoutMs)) { throw std::runtime_error("等待文件超时或服务器停止"); } // 读取文件内容 std::string content(file.content.data(), file.content.size()); // 查找第一行结尾 size_t pos = content.find('\n'); if (pos == std::string::npos) { // 没有换行符,整个内容作为第一行 outStr1 = content; outStr2 = ""; } else { // 提取第一行 outStr1 = content.substr(0, pos); // 提取第二行(跳过换行符) outStr2 = content.substr(pos + 1); // 移除第二行可能的多余换行符 if (!outStr2.empty() && outStr2.back() == '\n') { outStr2.pop_back(); } } return true; } bool Ucar::navigateTo(const geometry_msgs::Pose& target) { move_base_msgs::MoveBaseGoal goal; goal.target_pose.header.frame_id = "map"; goal.target_pose.pose = target; move_base_client_.sendGoal(goal); return move_base_client_.waitForResult(navigation_timeout_); } // void Ucar::recovery() { // setlocale(LC_ALL, ""); // std_srvs::Empty srv; // if (clear_costmaps_client_.call(srv)) { // ROS_INFO("代价地图清除成功"); // } else { // ROS_ERROR("代价地图清除服务调用失败"); // } // } Ucar::Ucar(ros::NodeHandle& nh) : nh_(nh), retry_count_(0), current_state_(State::INIT) , control_rate_(10) ,move_base_client_("move_base", true) { latest_odom_.reset(); // 等待move_base服务器就绪(参考网页6的actionlib使用规范) if(!move_base_client_.waitForServer(ros::Duration(5.0))) { setlocale(LC_ALL, ""); ROS_ERROR("无法连接move_base服务器"); } scanner.set_config(zbar::ZBAR_NONE, zbar::ZBAR_CFG_ENABLE, 1); // 初始化代价地图清除服务 clear_costmaps_client_ = nh_.serviceClient<std_srvs::Empty>("/move_base/clear_costmaps"); setlocale(LC_ALL, ""); // 标记所有的点位总共21个点位 nh_.param("pose1_x", pose_1.position.x, 1.67); nh_.param("pose1_y", pose_1.position.y, 0.04); pose_1.orientation.x = 0.0; pose_1.orientation.y = 0.0; pose_1.orientation.z = 0.707; pose_1.orientation.w = 0.707; nh_.param("pose2_x", pose_2.position.x, 1.83); nh_.param("pose2_y", pose_2.position.y, 0.380); pose_2.orientation.x = 0.0; pose_2.orientation.y = 0.0; pose_2.orientation.z = 1; pose_2.orientation.w = 0; nh_.param("pose3_x", pose_3.position.x, 1.0); nh_.param("pose3_y", pose_3.position.y, 0.494); pose_3.orientation.x = 0.0; pose_3.orientation.y = 0.0; pose_3.orientation.z = 1; pose_3.orientation.w = 0; nh_.param("pose4_x", pose_4.position.x, 0.15166891130059032); nh_.param("pose4_y", pose_4.position.y, 0.5446138339072089); pose_4.orientation.x = 0.0; pose_4.orientation.y = 0.0; pose_4.orientation.z = 0.707; pose_4.orientation.w = 0.707; nh_.param("pose5_x", pose_5.position.x, 0.387605134459779); nh_.param("pose5_y", pose_5.position.y, 0.949243539748394); pose_5.orientation.x = 0.0; pose_5.orientation.y = 0.0; pose_5.orientation.z = 0.0; pose_5.orientation.w = 1.0; nh_.param("pose6_x", pose_6.position.x, 1.0250469329539987); nh_.param("pose6_y", pose_6.position.y, 1.0430107266961729); pose_6.orientation.x = 0.0; pose_6.orientation.y = 0.0; pose_6.orientation.z = 0.0; pose_6.orientation.w = 1.0; nh_.param("pose7_x", pose_7.position.x, 1.715746358650675); nh_.param("pose7_y", pose_7.position.y, 1.0451169673664757); pose_7.orientation.x = 0.0; pose_7.orientation.y = 0.0; pose_7.orientation.z = 0.707; pose_7.orientation.w = 0.707; nh_.param("pose8_x", pose_8.position.x, 1.820954899866641); nh_.param("pose8_y", pose_8.position.y, 1.405578846446346); pose_8.orientation.x = 0.0; pose_8.orientation.y = 0.0; pose_8.orientation.z = 1; pose_8.orientation.w = 0; nh_.param("pose9_x", pose_9.position.x, 1.287663212010699); nh_.param("pose9_y", pose_9.position.y, 1.4502232396357953); pose_9.orientation.x = 0.0; pose_9.orientation.y = 0.0; pose_9.orientation.z = 1; pose_9.orientation.w = 0; nh_.param("pose10_x", pose_10.position.x, 0.433025661760874); nh_.param("pose10_y", pose_10.position.y, 1.5362058814619577); pose_10.orientation.x = 0.0; pose_10.orientation.y = 0.0; pose_10.orientation.z = 0.707; pose_10.orientation.w = 0.707; //开始进入视觉定位区域 中心点加上四个中线点对应四面墙 nh_.param("pose_center_x", pose_11.position.x, 1.13); nh_.param("pose_center_y", pose_11.position.y, 3.04); nh_.param("pose_center_x", pose_12.position.x, 1.13); nh_.param("pose_center_y", pose_12.position.y, 3.04); nh_.param("pose_center_x", pose_13.position.x, 1.13); nh_.param("pose_center_y", pose_13.position.y, 3.04); nh_.param("pose_center_x", pose_14.position.x, 1.13); nh_.param("pose_center_y", pose_14.position.y, 3.04); nh_.param("pose_center_x", pose_15.position.x, 1.13); nh_.param("pose_center_y", pose_15.position.y, 3.04); //退出视觉区域进入路灯识别区域 nh_.param("pose_center_x", pose_16.position.x, 1.13); nh_.param("pose_center_y", pose_16.position.y, 3.04); nh_.param("pose_center_x", pose_17.position.x, 1.13); nh_.param("pose_center_y", pose_17.position.y, 3.04); nh_.param("pose_center_x", pose_18.position.x, 1.13); nh_.param("pose_center_y", pose_18.position.y, 3.04); //退出路灯识别区域,进入巡线区域 注意两个点位取一个,必须标定正确的方向 nh_.param("pose_center_x", pose_19.position.x, 1.13); nh_.param("pose_center_y", pose_19.position.y, 3.04); pose_19.orientation.x = 0.0; pose_19.orientation.y = 0.0; pose_19.orientation.z = 0.707; pose_19.orientation.w = 0.707; nh_.param("pose_center_x", pose_20.position.x, 1.13); nh_.param("pose_center_y", pose_20.position.y, 3.04); pose_20.orientation.x = 0.0; pose_20.orientation.y = 0.0; pose_20.orientation.z = 0.707; pose_20.orientation.w = 0.707; //result_sub_ = nh_.subscribe("result_of_object", 1, &Ucar::detectionCallback, this); result_client_ = nh_.serviceClient<rfbot_yolov8_ros::DetectGood>("object_realsense_recognization"); cmd_vel_pub = nh_.advertise<geometry_msgs::Twist>("/cmd_vel", 10); odom_sub = nh_.subscribe("/odom", 10, &Ucar::odomCallback, this); interaction_client = nh_.serviceClient<ucar::ObjectDetection>("object_detection"); ROS_INFO_STREAM("状态机初始化完成"); } void Ucar::navigate_to_aruco_place(){ setlocale(LC_ALL, ""); navigateTo(pose_1);ROS_INFO("到达pose_1"); navigateTo(pose_2);ROS_INFO("到达pose_2"); navigateTo(pose_3);ROS_INFO("到达pose_3"); } void Ucar::navigate_to_recongnition_place(){ setlocale(LC_ALL, ""); navigateTo(pose_4);ROS_INFO("到达pose_4"); navigateTo(pose_5);ROS_INFO("到达pose_5"); navigateTo(pose_6);ROS_INFO("到达pose_6"); navigateTo(pose_7);ROS_INFO("到达pose_7"); navigateTo(pose_8);ROS_INFO("到达pose_8"); navigateTo(pose_9);ROS_INFO("到达pose_9"); navigateTo(pose_10);ROS_INFO("到达pose_10"); } void Ucar::run() { while (ros::ok()) { ros::spinOnce(); switch(current_state_) { case State::INIT: {handleInit(); break;} case State::ARUCO: {navigate_to_aruco_place(); ROS_INFO("导航到二维码区域"); see_aruco(); if(target_object=="vegetable") playAudioFile("/home/ucar/ucar_ws/src/wav/sc.wav"); if(target_object=="fruit") playAudioFile("/home/ucar/ucar_ws/src/wav/sg.wav"); if(target_object=="dessert") playAudioFile("/home/ucar/ucar_ws/src/wav/tp.wav"); break;} case State::FIND: {navigate_to_recongnition_place(); spin_to_find();//转着找目标 break;} case State::GAZEBO: {gazebo(); break;} } control_rate_.sleep(); } } void Ucar::handleInit() { setlocale(LC_ALL, ""); ROS_DEBUG("执行初始化流程"); current_state_ = State::ARUCO; } void Ucar::see_aruco() { qr_found_ = false; image_transport::ImageTransport it(nh_); // 2. 创建订阅器,订阅/usb_cam/image_raw话题 image_transport::Subscriber sub = it.subscribe( "/usb_cam/image_raw", 1, &Ucar::imageCallback, this ); std::cout << "已订阅摄像头话题,等待图像数据..." << std::endl; // 3. 创建ZBar扫描器 scanner.set_config(zbar::ZBAR_NONE, zbar::ZBAR_CFG_ENABLE, 1); target_object = ""; // 重置结果 // 4. 设置超时检测 const int MAX_SCAN_DURATION = 30; // 最大扫描时间(秒) auto scan_start_time = std::chrono::steady_clock::now(); // 5. ROS事件循环 ros::Rate loop_rate(30); // 30Hz while (ros::ok() && !qr_found_) { // 检查超时 auto elapsed = std::chrono::steady_clock::now() - scan_start_time; if (std::chrono::duration_cast<std::chrono::seconds>(elapsed).count() > MAX_SCAN_DURATION) { std::cout << "扫描超时" << std::endl; break; } ros::spinOnce(); loop_rate.sleep(); } // 6. 清理资源 if (!qr_found_) target_object = ""; } void Ucar::imageCallback(const sensor_msgs::ImageConstPtr& msg) { try { // 7. 将ROS图像消息转换为OpenCV格式 cv_bridge::CvImagePtr cv_ptr = cv_bridge::toCvCopy(msg, sensor_msgs::image_encodings::BGR8); cv::Mat frame = cv_ptr->image; cv::Mat gray; cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY); // 8. 准备ZBar图像 cv::Mat gray_cont = gray.clone(); zbar::Image image(gray.cols, gray.rows, "Y800", gray_cont.data, gray.cols * gray.rows); // 9. 扫描二维码 if (scanner.scan(image) >= 0) { for (zbar::Image::SymbolIterator symbol = image.symbol_begin(); symbol != image.symbol_end(); ++symbol) { // 获取原始结果 std::string rawResult = symbol->get_data(); // 在存储到全局变量前将首字母转换为小写 target_object = toLowerFirstChar(rawResult); ROS_INFO("识别到二维码: %s", target_object.c_str()); current_state_ = State::FIND; qr_found_ = true; // 在视频帧上绘制结果 std::vector<cv::Point> points; for (int i = 0; i < symbol->get_location_size(); i++) { points.push_back(cv::Point(symbol->get_location_x(i), symbol->get_location_y(i))); } // 绘制二维码边界框 cv::RotatedRect rect = cv::minAreaRect(points); cv::Point2f vertices[4]; rect.points(vertices); for (int i = 0; i < 4; i++) { cv::line(frame, vertices[i], vertices[(i + 1) % 4], cv::Scalar(0, 255, 0), 2); } // 显示原始二维码内容 cv::putText(frame, rawResult, cv::Point(10, 30), cv::FONT_HERSHEY_SIMPLEX, 0.7, cv::Scalar(0, 255, 0), 2); // 显示转换后的结果 cv::putText(frame, "Stored: " + target_object, cv::Point(10, 60), cv::FONT_HERSHEY_SIMPLEX, 0.7, cv::Scalar(0, 0, 255), 2); // 显示最终结果画面 cv::imshow("QR Code Scanner", frame); cv::waitKey(250); // 短暂显示结果 } } // 显示当前帧 cv::imshow("QR Code Scanner", frame); cv::waitKey(1); } catch (cv_bridge::Exception& e) { ROS_ERROR("cv_bridge异常: %s", e.what()); } } //播放音频文件函数 bool Ucar::playAudioFile(const std::string& filePath) { // 创建子进程播放音频 pid_t pid = fork(); if (pid == 0) { // 子进程 // 尝试使用 aplay (适用于 WAV 文件) execlp("aplay", "aplay", "-q", filePath.c_str(), (char*)NULL); // 如果 aplay 失败,尝试 ffplay execlp("ffplay", "ffplay", "-nodisp", "-autoexit", "-loglevel", "quiet", filePath.c_str(), (char*)NULL); // 如果 ffplay 失败,尝试 mpg123 (适用于 MP3) execlp("mpg123", "mpg123", "-q", filePath.c_str(), (char*)NULL); // 如果所有播放器都不可用,退出 exit(1); } else if (pid > 0) { // 父进程 int status; waitpid(pid, &status, 0); return WIFEXITED(status) && WEXITSTATUS(status) == 0; } else { // fork 失败 perror("fork failed"); return false; } } // 辅助函数:将字符串首字母转换为小写 std::string Ucar::toLowerFirstChar(const std::string& str) { if (str.empty()) return str; std::string result = str; result[0] = static_cast<char>(std::tolower(static_cast<unsigned char>(result[0]))); return result; } // 在odomCallback中更新最新里程计数据 void Ucar::odomCallback(const nav_msgs::Odometry::ConstPtr& msg) { latest_odom_ = msg; // 保存最新里程计数据 if (!position_recorded) { initial_position = msg->pose.pose.position; position_recorded = true; //ROS_INFO("初始位置记录完成: (%.3f, %.3f)", // initial_position.x, initial_position.y); } } void Ucar::moveLeftDistance(double distance_m, double linear_speed) { // 订阅里程计话题(根据实际机器人配置修改话题名) // 等待位置记录完成 position_recorded = false; while (!position_recorded && ros::ok()) { ros::spinOnce(); ros::Duration(0.1).sleep(); } // 计算理论运动时间 double duration = distance_m / linear_speed; //ROS_INFO("开始左移: 距离=%.2fm, 速度=%.2fm/s, 理论时间=%.1fs", // distance_m, linear_speed, duration); // 创建控制消息 geometry_msgs::Twist move_cmd; move_cmd.linear.y = linear_speed; // 全向移动Y轴速度控制左右移动 // 记录开始时间 auto start_time = ros::Time::now(); // 持续发送控制命令 while ((ros::Time::now() - start_time).toSec() < duration && ros::ok()) { cmd_vel_pub.publish(move_cmd); ros::Duration(0.05).sleep(); // 50ms控制周期 } // 发送停止指令 move_cmd.linear.y = 0; cmd_vel_pub.publish(move_cmd); //ROS_INFO("左移完成"); } void Ucar::moveRightDistance(double distance_m, double linear_speed) { // 等待位置记录完成 position_recorded = false; while (!position_recorded && ros::ok()) { ros::spinOnce(); ros::Duration(0.1).sleep(); } // 计算理论运动时间 double duration = distance_m / linear_speed; //ROS_INFO("开始右移: 距离=%.2fm, 速度=%.2fm/s, 理论时间=%.1fs", // distance_m, linear_speed, duration); // 创建控制消息 geometry_msgs::Twist move_cmd; move_cmd.linear.y = -linear_speed; // 全向移动Y轴速度控制左右移动 // 记录开始时间 auto start_time = ros::Time::now(); // 持续发送控制命令 while ((ros::Time::now() - start_time).toSec() < duration && ros::ok()) { cmd_vel_pub.publish(move_cmd); ros::Duration(0.05).sleep(); // 50ms控制周期 } // 发送停止指令 move_cmd.linear.y = 0; cmd_vel_pub.publish(move_cmd); //ROS_INFO("右移完成"); } void Ucar::moveForwardDistance(double distance_m, double linear_speed) { // 确保速度为正值(前进方向) if (linear_speed < 0) { ROS_WARN("前进速度应为正值,自动取绝对值"); linear_speed = fabs(linear_speed); } // 等待位置记录完成 position_recorded = false; while (!position_recorded && ros::ok()) { ros::spinOnce(); ros::Duration(0.1).sleep(); } // 计算理论运动时间 double duration = distance_m / linear_speed; //ROS_INFO("开始前进: 距离=%.2fm, 速度=%.2fm/s, 理论时间=%.1fs", // distance_m, linear_speed, duration); // 创建控制消息 geometry_msgs::Twist move_cmd; move_cmd.linear.x = linear_speed; // X轴速度控制前后移动 // 记录开始时间 auto start_time = ros::Time::now(); // 持续发送控制命令 while ((ros::Time::now() - start_time).toSec() < duration && ros::ok()) { cmd_vel_pub.publish(move_cmd); ros::Duration(0.05).sleep(); // 50ms控制周期 } // 发送停止指令 move_cmd.linear.x = 0.0; cmd_vel_pub.publish(move_cmd); ROS_INFO("前进完成"); } //输入1逆时针,输入-1顺时针 void Ucar::rotateCounterClockwise5Degrees(int a) { // 设置旋转参数 const double angular_speed = 0.2; // rad/s (约11.5度/秒) const double degrees = 5.0; const double duration = degrees * (M_PI / 180.0) / angular_speed; // 约0.436秒 //if(a==1) //ROS_INFO("开始逆时针旋转5度: 速度=%.2f rad/s, 时间=%.3f秒", // angular_speed, duration); //else if(a==-1) //ROS_INFO("开始顺时针旋转5度: 速度=%.2f rad/s, 时间=%.3f秒", // angular_speed, duration); // 创建控制消息 geometry_msgs::Twist twist_msg; twist_msg.angular.z = angular_speed*a; // 正值表示逆时针 // 记录开始时间 auto start_time = ros::Time::now(); // 持续发送控制命令 while ((ros::Time::now() - start_time).toSec() < duration && ros::ok()) { cmd_vel_pub.publish(twist_msg); ros::Duration(0.05).sleep(); // 20Hz控制周期 } // 发送停止命令(确保接收) twist_msg.angular.z = 0.0; for (int i = 0; i < 3; i++) { cmd_vel_pub.publish(twist_msg); ros::Duration(0.02).sleep(); } if(a==1) ROS_INFO("逆时针旋转5度完成"); else if (a==-1) ROS_INFO("顺时针旋转5度完成"); } void Ucar::rotateCounterClockwise360Degrees() { // 设置旋转参数 const double angular_speed = 1; // rad/s (约11.5度/秒) const double degrees = 360.0; const double duration = degrees * (M_PI / 180.0) / angular_speed; // 约0.436秒 // 创建控制消息 geometry_msgs::Twist twist_msg; twist_msg.angular.z = angular_speed; // 正值表示逆时针 // 记录开始时间 auto start_time = ros::Time::now(); // 持续发送控制命令 while ((ros::Time::now() - start_time).toSec() < duration && ros::ok()) { cmd_vel_pub.publish(twist_msg); ros::Duration(0.05).sleep(); // 20Hz控制周期 } // 发送停止命令(确保接收) twist_msg.angular.z = 0.0; for (int i = 0; i < 3; i++) { cmd_vel_pub.publish(twist_msg); ros::Duration(0.02).sleep(); } } void Ucar::left_and_right_move_old(){ rfbot_yolov8_ros::DetectGood srv; while((find_object_x2_old+find_object_x1_old)/2>324){ if((find_object_x2_old+find_object_x1_old)>340) moveLeftDistance(0.15,0.1);//控制小车往左移动15cm else moveLeftDistance(0.05,0.1);//控制小车往左移动5cm moveForwardDistance(0.05,0.1); Ucar::result_client_.call(srv); for (size_t j = 0; j < srv.response.goodName.size(); ++j) { ROS_INFO("响应结果:"); ROS_INFO("位置: x1: %d, y1: %d, x2: %d, y2: %d", srv.response.u1[j], srv.response.v1[j], srv.response.u2[j], srv.response.v2[j]); //ROS_INFO("置信度: %f", srv.response.confidence[j]); find_object_x1_old=srv.response.u1[0]; find_object_y1_old=srv.response.v1[0]; find_object_x2_old=srv.response.u2[0]; find_object_y2_old=srv.response.v2[0]; } ROS_INFO("当前目标中心横坐标为:%d",(find_object_x2_old+find_object_x1_old)/2); } while((find_object_x2_old+find_object_x1_old)/2<316){ if((find_object_x2_old+find_object_x1_old)<300) moveRightDistance(0.15,0.1);//控制小车往右移动15cm else moveRightDistance(0.05,0.1);//控制小车往右移动5cm moveForwardDistance(0.05,0.1); Ucar::result_client_.call(srv); for (size_t j = 0; j < srv.response.goodName.size(); ++j) { ROS_INFO("响应结果:"); ROS_INFO("位置: x1: %d, y1: %d, x2: %d, y2: %d", srv.response.u1[j], srv.response.v1[j], srv.response.u2[j], srv.response.v2[j]); //ROS_INFO("置信度: %f", srv.response.confidence[j]); find_object_x1_old=srv.response.u1[0]; find_object_y1_old=srv.response.v1[0]; find_object_x2_old=srv.response.u2[0]; find_object_y2_old=srv.response.v2[0]; } ROS_INFO("当前目标中心横坐标为:%d",(find_object_x2_old+find_object_x1_old)/2); } ROS_INFO("左右移动完成"); } //前进函数(涵盖第二种停靠算法) void Ucar::go(){ rfbot_yolov8_ros::DetectGood srv; while(1){ moveForwardDistance(0.05,0.1);//控制小车前进 Ucar::result_client_.call(srv); for (size_t j = 0; j < srv.response.goodName.size(); ++j) { ROS_INFO("响应结果:"); ROS_INFO("位置: x1: %d, y1: %d, x2: %d, y2: %d", srv.response.u1[j], srv.response.v1[j], srv.response.u2[j], srv.response.v2[j]); //ROS_INFO("置信度: %f", srv.response.confidence[j]); find_object_x1_new=srv.response.u1[0]; find_object_y1_new=srv.response.v1[0]; find_object_x2_new=srv.response.u2[0]; find_object_y2_new=srv.response.v2[0]; } //图像左边先到达边线——>逆时针往右 if(find_object_x2_new==640&&find_object_y1_new==0&&find_object_x1_new!=0&&find_object_y2_new>=350){ if(find_object_x1_new>240||(find_object_x2_new-find_object_x1_new)<=(find_object_y2_new-find_object_y1_new)){ rotateCounterClockwise5Degrees(1); rotateCounterClockwise5Degrees(1); rotateCounterClockwise5Degrees(1); rotateCounterClockwise5Degrees(1); rotateCounterClockwise5Degrees(1); moveRightDistance(0.35,0.1); ROS_INFO("大摆头"); break; } else if(find_object_x1_new>120){ rotateCounterClockwise5Degrees(1); rotateCounterClockwise5Degrees(1); rotateCounterClockwise5Degrees(1); moveRightDistance(0.25,0.1); ROS_INFO("中摆头"); break; } else{ rotateCounterClockwise5Degrees(1);//逆时针 moveRightDistance(0.1,0.1); ROS_INFO("小摆头"); break; } } //图像右边先到达边线——>顺时针往左 else if(find_object_x1_new==0&&find_object_y1_new==0&&find_object_x2_new!=640&&find_object_y2_new>=350){ if(find_object_x2_new<400||(find_object_x2_new-find_object_x1_new)<=(find_object_y2_new-find_object_y1_new)){ rotateCounterClockwise5Degrees(-1); rotateCounterClockwise5Degrees(-1); rotateCounterClockwise5Degrees(-1); rotateCounterClockwise5Degrees(-1); rotateCounterClockwise5Degrees(-1); moveLeftDistance(0.35,0.1); ROS_INFO("大摆头"); break; } else if(find_object_x2_new<520){ rotateCounterClockwise5Degrees(-1); rotateCounterClockwise5Degrees(-1); rotateCounterClockwise5Degrees(-1); moveLeftDistance(0.25,0.1); ROS_INFO("中摆头"); break; } else{ rotateCounterClockwise5Degrees(-1);//顺时针 moveLeftDistance(0.1,0.1); ROS_INFO("小摆头"); break; } } } } void Ucar::visualservo(){ rfbot_yolov8_ros::DetectGood srv; //左移右移 left_and_right_move_old(); //提取长宽比 Ucar::result_client_.call(srv); for (size_t j = 0; j < srv.response.goodName.size(); ++j) { ROS_INFO("响应结果:"); ROS_INFO("位置: x1: %d, y1: %d, x2: %d, y2: %d", srv.response.u1[j], srv.response.v1[j], srv.response.u2[j], srv.response.v2[j]); //ROS_INFO("置信度: %f", srv.response.confidence[j]); find_object_x1_old=srv.response.u1[0]; find_object_y1_old=srv.response.v1[0]; find_object_x2_old=srv.response.u2[0]; find_object_y2_old=srv.response.v2[0]; } changkuanbi_old=(find_object_x2_old-find_object_x1_old)/(find_object_y2_old-find_object_y1_old); ROS_INFO("长宽比为:%f",changkuanbi_old);go(); if(find_object=="dessert1") playAudioFile("/home/ucar/ucar_ws/src/wav/kl.wav"); if(find_object=="dessert2") playAudioFile("/home/ucar/ucar_ws/src/wav/dg.wav"); if(find_object=="dessert3") playAudioFile("/home/ucar/ucar_ws/src/wav/nn.wav"); if(find_object=="vegetable1") playAudioFile("/home/ucar/ucar_ws/src/wav/lj.wav"); if(find_object=="vegetable2") playAudioFile("/home/ucar/ucar_ws/src/wav/xhs.wav"); if(find_object=="vegetable3") playAudioFile("/home/ucar/ucar_ws/src/wav/td.wav"); if(find_object=="fruit1") playAudioFile("/home/ucar/ucar_ws/src/wav/xj.wav"); if(find_object=="fruit2") playAudioFile("/home/ucar/ucar_ws/src/wav/pg.wav"); if(find_object=="fruit3") playAudioFile("/home/ucar/ucar_ws/src/wav/xg.wav"); // if(abs(changkuanbi_old-1.666666667)<0.05){ // ROS_INFO("比较接近16:9,不需要旋转"); // //前进 // go(); // } // else { // //先逆时针转个10度 // rotateCounterClockwise5Degrees(1); // rotateCounterClockwise5Degrees(1); // Ucar::result_client_.call(srv); // for (size_t j = 0; j < srv.response.goodName.size(); ++j) { // ROS_INFO("响应结果:"); // ROS_INFO("位置: x1: %d, y1: %d, x2: %d, y2: %d", // srv.response.u1[j], // srv.response.v1[j], // srv.response.u2[j], // srv.response.v2[j]); // //ROS_INFO("置信度: %f", srv.response.confidence[j]); // find_object_x1_new=srv.response.u1[0]; // find_object_y1_new=srv.response.v1[0]; // find_object_x2_new=srv.response.u2[0]; // find_object_y2_new=srv.response.v2[0]; // } // changkuanbi_new=(find_object_x2_new-find_object_x1_new)/(find_object_y2_new-find_object_y1_new); // ROS_INFO("长宽比为:%f",changkuanbi_new); // if(changkuanbi_new<changkuanbi_old)//方向错了 // { // while(abs(changkuanbi_new-1.666666667)>0.4)//不准就再转 // { // rotateCounterClockwise5Degrees(-1); // Ucar::result_client_.call(srv); // for (size_t j = 0; j < srv.response.goodName.size(); ++j) { // ROS_INFO("响应结果:"); // ROS_INFO("位置: x1: %d, y1: %d, x2: %d, y2: %d", // srv.response.u1[j], // srv.response.v1[j], // srv.response.u2[j], // srv.response.v2[j]); // //ROS_INFO("置信度: %f", srv.response.confidence[j]); // find_object_x1_new=srv.response.u1[0]; // find_object_y1_new=srv.response.v1[0]; // find_object_x2_new=srv.response.u2[0]; // find_object_y2_new=srv.response.v2[0]; // //保持正对目标 // while((find_object_x2_new+find_object_x1_new)/2>324) // {moveLeftDistance(0.05,0.1);//控制小车往左移动5cm // Ucar::result_client_.call(srv); // for (size_t j = 0; j < srv.response.goodName.size(); ++j) { // ROS_INFO("响应结果:"); // ROS_INFO("位置: x1: %d, y1: %d, x2: %d, y2: %d", // srv.response.u1[j], // srv.response.v1[j], // srv.response.u2[j], // srv.response.v2[j]); // //ROS_INFO("置信度: %f", srv.response.confidence[j]); // find_object_x1_new=srv.response.u1[0]; // find_object_y1_new=srv.response.v1[0]; // find_object_x2_new=srv.response.u2[0]; // find_object_y2_new=srv.response.v2[0]; // } // changkuanbi_new=(find_object_x2_new-find_object_x1_new)/(find_object_y2_new-find_object_y1_new); // ROS_INFO("长宽比为:%f",changkuanbi_new);} // while((find_object_x2_new+find_object_x1_new)/2<316) // {moveRightDistance(0.05,0.1);//控制小车往右移动5cm // Ucar::result_client_.call(srv); // for (size_t j = 0; j < srv.response.goodName.size(); ++j) { // ROS_INFO("响应结果:"); // ROS_INFO("位置: x1: %d, y1: %d, x2: %d, y2: %d", // srv.response.u1[j], // srv.response.v1[j], // srv.response.u2[j], // srv.response.v2[j]); // //ROS_INFO("置信度: %f", srv.response.confidence[j]); // find_object_x1_new=srv.response.u1[0]; // find_object_y1_new=srv.response.v1[0]; // find_object_x2_new=srv.response.u2[0]; // find_object_y2_new=srv.response.v2[0]; // } // changkuanbi_new=(find_object_x2_new-find_object_x1_new)/(find_object_y2_new-find_object_y1_new); // ROS_INFO("长宽比为:%f",changkuanbi_new);} // } // } // } // else{//方向对了 // while(abs(changkuanbi_new-1.666666667)>0.4)//不准就再转 // { // rotateCounterClockwise5Degrees(1); // //保持正对目标 // while((find_object_x2_new+find_object_x1_new)/2>324) moveLeftDistance(0.05,0.1);//控制小车往左移动5cm // while((find_object_x2_new+find_object_x1_new)/2<316) moveRightDistance(0.05,0.1);//控制小车往右移动5cm // Ucar::result_client_.call(srv); // for (size_t j = 0; j < srv.response.goodName.size(); ++j) { // ROS_INFO("响应结果:"); // ROS_INFO("位置: x1: %d, y1: %d, x2: %d, y2: %d", // srv.response.u1[j], // srv.response.v1[j], // srv.response.u2[j], // srv.response.v2[j]); // //ROS_INFO("置信度: %f", srv.response.confidence[j]); // find_object_x1_new=srv.response.u1[0]; // find_object_y1_new=srv.response.v1[0]; // find_object_x2_new=srv.response.u2[0]; // find_object_y2_new=srv.response.v2[0]; // } // changkuanbi_new=(find_object_x2_new-find_object_x1_new)/(find_object_y2_new-find_object_y1_new); // ROS_INFO("长宽比为:%f",changkuanbi_new); // } // } // //前进 // go(); // } ROS_INFO("导航完成"); ros::Duration(3000).sleep(); current_state_ = State::GAZEBO; } void Ucar::spin_to_find(){ setlocale(LC_ALL, ""); for(int i=0;i<9;i++){ navigateTo(pose_center[i]);//导航到i号点位 ros::Duration(3).sleep();//停留3秒 rfbot_yolov8_ros::DetectGood srv; Ucar::result_client_.call(srv); for (size_t j = 0; j < srv.response.goodName.size(); ++j) { ROS_INFO("响应结果:"); ROS_INFO("位置: x1: %d, y1: %d, x2: %d, y2: %d", srv.response.u1[j], srv.response.v1[j], srv.response.u2[j], srv.response.v2[j]); //ROS_INFO("置信度: %f", srv.response.confidence[j]); find_object_x1_old=srv.response.u1[0]; find_object_y1_old=srv.response.v1[0]; find_object_x2_old=srv.response.u2[0]; find_object_y2_old=srv.response.v2[0]; pose_center_object[i] = srv.response.goodName[0]; //看看这个方向有没有目标 } if(pose_center_object[i].find(target_object)!=std::string::npos&&(find_object_x2_old-find_object_x1_old)>=(find_object_y2_old-find_object_y1_old)) {ROS_INFO("本次任务目标识别成功"); find_object=pose_center_object[i]; visualservo(); break;} else{ ROS_INFO("本次任务目标识别失败,尝试下一个目标"); continue; } } } void Ucar::gazebo(){ navigateTo(pose_gazebo); //主从机通信 FileTransfer ft("/home/ucar/ucar_ws/src/ucar"); ft.saveToFile(target_object, "target.txt"); //电脑的ip ft.sendTo("192.168.1.100", 8080, "/home/ucar/ucar_ws/src/ucar/target.txt", "/home/zzs/gazebo_test_ws/src/ucar2/target.txt"); try { // 示例3:等待接收文件并读取内容 std::cout << "等待接收文件..." << std::endl; ft.receiveAndReadTwoStrings(8080, gazebo_object, gazebo_room); std::cout << "接收到的内容:\n" << gazebo_object << "\n"<<gazebo_room << std::endl; } catch (const std::exception& e) { std::cerr << "错误: " << e.what() << std::endl;} ros::Duration(30000).sleep();//停留3秒 } int main(int argc, char** argv) { ros::init(argc, argv, "multi_room_navigation_node"); ros::NodeHandle nh; Ucar ucar(nh); ucar.run(); return 0; }修改上述代码,在nh_.param("pose_center_x", pose_11.position.x, 1.13); nh_.param("pose_center_y", pose_11.position.y, 3.04); nh_.param("pose_center_x", pose_12.position.x, 1.16); nh_.param("pose_center_y", pose_12.position.y, 2.04); nh_.param("pose_center_x", pose_13.position.x, 5.13); nh_.param("pose_center_y", pose_13.position.y, 5.04); nh_.param("pose_center_x", pose_14.position.x, 3.13); nh_.param("pose_center_y", pose_14.position.y, 2.04); nh_.param("pose_center_x", pose_15.position.x, 1.13); nh_.param("pose_center_y", pose_15.position.y, 3.04);视觉定位区域对应的五个点位中,添加一个函数,只有到达这五个点位的时候开始旋转,在到达其中一个点位的时候,开始旋转速度不用很快,每隔0.3秒停顿0.5秒的时间识别目标,在视觉到目标之后触发视觉定位前往目标,没有识别到目标前往下一个点位,在识别到目标之后不前往下一个点位,不要修改move_base,只针对这五个点添加函数

最新推荐

recommend-type

基于COMSOL的压裂井降压开采数值模拟:地层压力与流场分布研究

利用COMSOL Multiphysics进行压裂井降压开采的数值模拟过程。首先,通过对特定油藏地区的岩层性质和流体分布进行初步勘测和分析,建立了三维地质模型,考虑了地层层次结构、岩石物理性质(如渗透率、孔隙度)和流体属性(如粘度、密度)。接着,在模型中设定了流体在多孔介质中流动的物理场,并配置了适当的流体源和压力边界条件。随后,使用流体流动和固体力学模块模拟了压裂过程,观察到裂缝的形成和扩展及其对周围地层的影响。最后,展示了模拟得到的地层压力和流场分布图,并附上了一段简化的COMSOL代码示例,解释了如何设置物理场和边界条件。通过这次模拟,不仅可以熟悉压裂开采的模拟方法,还能将其应用于油藏开采和地热开采等领域。 适合人群:从事能源开采领域的研究人员和技术人员,尤其是对压裂井降压开采感兴趣的从业者。 使用场景及目标:适用于希望深入了解压裂井降压开采过程的研究人员和技术人员,旨在提高对地下流体运动和压力变化的理解,优化开采过程。 其他说明:文中提供的代码示例有助于读者快速入门COMSOL的流体流动仿真设置,同时模拟结果为实际开采提供了宝贵的数据支持。
recommend-type

工业自动化领域汇川中大型PLC(AM600AM400AC800)模版的模块化编程及其应用

内容概要:本文介绍了汇川中大型PLC模版(AM600、AM400、AC800)的特点和优势,重点阐述了模块化编程方法的应用。文中提到,这些模版不仅结构清晰、稳定可靠,而且易于扩展和维护。此外,文章还提供了一个新能源项目的PLC程序实例,展示了如何利用指针和for循环遍历的方式简化多轴控制系统的设计。同时,文中附带了空模版和实际案例模版,便于初学者和从业者快速上手并应用于实际项目中。 适合人群:从事工业自动化领域的工程师和技术人员,特别是那些希望提高PLC编程技能或者正在寻找高效编程解决方案的专业人士。 使用场景及目标:①学习和掌握汇川中大型PLC模版的模块化编程技巧;②通过实际案例加深对多轴控制系统的理解;③利用提供的模版加速新项目的开发进程。 其他说明:文中提供的程序实例和模版有助于读者更好地理解和实践PLC编程,但需要注意的是,软件一旦售出不可退换。
recommend-type

前端分析-2023071100789s

前端分析-2023071100789s
recommend-type

基于MATLAB的ACO蚁群算法优化BP神经网络用于电厂负荷预测及性能对比

内容概要:本文介绍了利用ACO蚁群算法优化BP神经网络(ACO-BPNN)进行回归预测的方法及其MATLAB实现。文中详细解释了ACO与BP神经网络结合的工作机制,展示了如何通过调整蚁群算法中的参数来改善BP神经网络易陷入局部最优解的问题。具体实现了对电厂运行数据的读取、预处理以及优化前后预测精度的对比实验,结果显示优化后的模型将预测误差从8.7%降低到了3.2%。 适合人群:对机器学习尤其是神经网络优化感兴趣的科研工作者和技术爱好者,以及从事电力系统数据分析的专业人士。 使用场景及目标:适用于需要提高预测准确性并希望探索不同优化方法的应用场合,如能源管理系统的负荷预测。目标是帮助用户理解ACO-BPNN的工作流程,掌握其实现技巧,并能够应用于实际项目中。 其他说明:文中提供了完整的MATLAB代码片段,包括数据加载、预处理、模型构建、训练和评估等步骤,并附带详细的注释以便于理解和修改。同时提醒了一些常见的错误避免措施,如正确设置蚂蚁数量、信息素初始值范围以及确保Excel数据无空值等问题。
recommend-type

GHCN气象站邻接矩阵的Python实现及地理距离应用

根据提供的文件信息,我们可以解析出以下知识点: **标题:“GHCN_邻接矩阵”** 全球历史气候网络(Global Historical Climatology Network,简称GHCN)是一个国际性项目,旨在收集和提供全球范围内的历史气候数据。邻接矩阵(Adjacency Matrix)是图论中的一个概念,用来表示图中各个顶点之间的相邻关系。 **知识点详细说明:** 1. **全球历史气候网络(GHCN):** - GHCN是一个汇集了全球范围内的历史气候数据资料的大型数据库。该数据库主要收集了全球各地的气象站提供的气温、降水、风速等气象数据。 - 这些数据的时间跨度很广,有些甚至可以追溯到19世纪中叶,为气候学家和相关研究人员提供了丰富的气候变迁数据。 - 通过分析这些数据,科学家可以研究气候变化的趋势、模式以及影响因素等。 2. **邻接矩阵:** - 在图论中,邻接矩阵是用来表示图中各个顶点之间相互连接关系的矩阵。 - 无向图的邻接矩阵是一个对称矩阵,如果顶点i与顶点j之间存在一条边,则矩阵中的元素A[i][j]和A[j][i]为1;否则为0。 - 邻接矩阵常用于计算机算法中,比如用于计算最短路径、网络的连通性、以及进行图的遍历等。 3. **地理距离:** - 在这个问题的上下文中,指的是气象站之间的空间距离。 - 计算气象站之间的地理距离通常使用地理信息系统(GIS)或球面几何学的方法,比如使用哈弗辛公式(Haversine formula)计算两个地点之间的大圆距离。 - 通过地理距离数据,可以推断出气候数据在空间分布上的相关性或依赖性。 4. **Python编程语言:** - 标签中提及的Python是一种广泛应用于数据科学、人工智能、网络开发等领域的高级编程语言。 - Python因其易学易用、语法简洁、库支持丰富等特点,在科研、教育、工业界等领域得到广泛应用。 5. **代码实现:** - 提到的代码应该会涉及获取GHCN数据集、计算气象站间的地理距离、以及根据这些距离构建无向图的邻接矩阵。 - 代码可能使用了Python中的科学计算库,如NumPy或SciPy,以及地理计算库,如geopy或Shapely。 - 通过构建邻接矩阵,此代码可以进一步用于分析气候数据的空间分布特征或执行图相关的数据分析任务。 **文件名称列表:“GHCN_Adjacency_Matrix-main”** 文件名“GHCN_Adjacency_Matrix-main”表明这是一个包含主要代码文件的文件夹。在Python中,“main”一词通常表示该文件夹中的某个脚本文件(如“main.py”)是程序的入口点,它是运行程序时最先被调用的文件。 总结以上内容,我们可知该文件描述了一个使用Python语言,通过处理全球历史气候网络(GHCN)中的数据来生成气象站之间地理距离的邻接矩阵的代码。这种邻接矩阵可以用于气候数据分析中的图论应用,如研究气象站点数据的空间分布模式和关系。代码的实现可能用到了多种Python库,旨在为气候研究提供一种新的数据处理和分析视角。
recommend-type

并发编程:多语言实践与策略选择

### 并发编程:多语言实践与策略选择 #### 1. 文件大小计算的并发实现 在并发计算文件大小的场景中,我们可以采用数据流式方法。具体操作如下: - 创建两个 `DataFlowQueue` 实例,一个用于记录活跃的文件访问,另一个用于接收文件和子目录的大小。 - 创建一个 `DefaultPGroup` 来在线程池中运行任务。 ```plaintext graph LR A[创建 DataFlowQueue 实例] --> B[创建 DefaultPGroup] B --> C[执行 findSize 方法] C --> D[执行 findTotalFileS
recommend-type

3utools使用教程

<think>我们注意到用户查询的是"3utools",但引用中提到的都是"uTools"(没有数字3)。这可能是一个拼写错误或版本差异。 根据引用内容,uTools是一个桌面效率工具,提供插件化的功能。因此,我们假设用户指的是uTools,并据此提供使用指南。 如果确实存在名为"3uTools"的软件(例如,苹果设备管理工具),那么我们需要澄清:引用中提到的uTools与3uTools不同。 但是,由于引用中只提到了uTools,且用户的问题中写的是“3utools”(可能是uTools的误写),我们将按照uTools来回答。 如果用户确实指的是另一个软件(3uTools),那么
recommend-type

探索更多视频功能的JavaScript实现

从给定的信息中我们可以提炼出关键词"更多视频"以及"JavaScript",以及一个与文件相关的命名"MoreVideo-master"。接下来,我会针对这些关键词展开详细的IT知识点阐述。 首先,关于“更多视频”,这个描述暗示了我们即将探讨的是与视频内容相关的技术或应用。在现代IT领域中,视频内容的处理、存储、传输和播放是一个非常重要的分支,涉及到的技术包括但不限于视频编码、流媒体技术、网络协议、前端展示技术等。视频内容的增多以及互联网带宽的不断提升,使得在线视频消费成为可能。从最早的ASCII动画到现代的高清视频,技术的演进一直不断推动着我们向更高质量和更多样化的视频内容靠近。 其次,“JavaScript”是IT行业中的一个关键知识点。它是一种广泛使用的脚本语言,特别适用于网页开发。JavaScript可以实现网页上的动态交互,比如表单验证、动画效果、异步数据加载(AJAX)、以及单页应用(SPA)等。作为一种客户端脚本语言,JavaScript可以对用户的输入做出即时反应,无需重新加载页面。此外,JavaScript还可以运行在服务器端(例如Node.js),这进一步拓宽了它的应用范围。 在探讨JavaScript时,不得不提的是Web前端开发。在现代的Web应用开发中,前端开发越来越成为项目的重要组成部分。前端开发人员需要掌握HTML、CSS和JavaScript这三大核心技术。其中,JavaScript负责赋予网页以动态效果,提升用户体验。JavaScript的库和框架也非常丰富,比如jQuery、React、Vue、Angular等,它们可以帮助开发者更加高效地编写和管理前端代码。 最后,关于文件名“MoreVideo-master”,这里的“Master”通常表示这是一个项目或者源代码的主版本。例如,在使用版本控制系统(如Git)时,“Master”分支通常被认为是项目的主分支,包含最新的稳定代码。文件名中的“MoreVideo”表明该项目与视频相关的内容处理功能正在增加或扩展。可能是对现有功能的增强,也可能是为视频播放、视频处理或视频管理增加了新的模块或特性。 综合上述内容,我们可以总结出以下几个IT知识点: 1. 视频技术:包括视频编解码技术、流媒体技术、网络协议、视频格式转换等。在客户端和服务器端,视频技术的应用场景广泛,如在线视频平台、视频会议系统、视频监控系统等。 2. JavaScript应用:JavaScript在Web前端开发中的应用十分广泛,用于实现网页的动态效果和交互性,以及在后端通过Node.js提供服务器端编程能力。 3. 前端开发技术:前端开发不仅仅是页面的静态显示,更重要的是通过JavaScript、CSS和HTML等技术实现用户界面与用户之间的交互。前端框架和库的使用大大提高了开发效率。 4. 版本控制:在IT项目开发过程中,版本控制是一个重要的环节。它帮助开发者管理代码的变更历史,支持多人协作开发,使得代码维护和升级变得更加容易。 通过以上知识点,我们可以看到,无论是“更多视频”的技术应用还是“JavaScript”编程语言的使用,以及文件命名所隐含的项目管理概念,都是IT领域内相互关联且密不可分的几个重要方面。
recommend-type

并发编程中的Clojure代理与相关技术解析

### 并发编程中的Clojure代理与相关技术解析 #### 1. Clojure代理概述 Clojure代理代表内存中的单个标识或位置。与软件事务内存(STM)的引用管理多个标识的协调同步更改不同,代理允许对其管理的单个标识进行独立的异步更改。这些更改以函数或操作的形式表达,并异步应用于该位置。多个独立的并发操作会按顺序依次运行。操作成功完成后,代理将更新为操作返回的新状态,该新状态用于后续对代理的读取或操作。 调用在代理上运行操作的方法会立即返回,操作随后会使用Clojure管理的线程池中的一个线程应用于代理。如果操作主要受CPU限制,可使用`send()`方法运行;如果函数中可能发
recommend-type

windows电脑如何安装华为电脑管家

<think>首先,用户的问题是:“我想在Windows电脑上安装华为电脑管家 请问如何在Windows系统上安装和配置华为电脑管家”。这基于之前的引用,是关于在非华为电脑上安装华为电脑管家的指南。 引用提供了几个来源: - [^1]:非华为电脑安装华为电脑管家指南,项目地址:https://gitcode.com/open-source-toolkit/90481 - [^2]:win10或11非华为电脑安装最新的电脑管家,包括安装方法和问题解决 - [^3]:华为电脑管家傻瓜一键安装版,适用于win10,支持非华为电脑 - [^4]:提供旧版本华为电脑管家的链接和卸载方法 - [^5]: