基于3×3卷积滤波器的旋转正方形角点检测:单像素角点提取实现疑问
解决正方形角点单像素标记问题
你已经完成了最关键的一步——用方向滤波器生成了四个角点的响应图,现在只需要把这些连续的响应值转换成每个角点对应的单个像素点。核心思路是提取每个响应图中的局部最大值(因为真正的角点位置会是响应最强的点),再过滤掉噪声,最终得到精准的单像素标记。
关键步骤解析
1. 非极大值抑制(NMS)
每个响应图中,真正的角点是局部区域内响应最强的像素,我们需要用非极大值抑制把连续的响应区域压缩到单个像素:
- 对每个通道的响应图做膨胀操作,得到每个位置的局部最大值
- 只保留原响应图中等于局部最大值的像素,其他置0,这样就得到了仅含局部峰值的图
2. 阈值筛选
设置一个合理的响应阈值,过滤掉响应值过低的噪声点,只保留真正的角点候选。阈值可以根据你的测试图像调整。
3. (可选)角点匹配与验证
如果图像中有多个正方形,你可以进一步对候选点进行聚类,确保每个正方形的四个角点(上、右、左、下)一一对应,不过如果正方形不重叠,这一步可以简化。
修改后的完整代码
import numpy as np import cv2 as cv from pathlib import Path UTILS = Path("./") # 根据你的实际路径修改 def non_max_suppression(response_map, kernel_size=3): """对响应图应用非极大值抑制,保留局部最大值点""" kernel = np.ones((kernel_size, kernel_size), dtype=np.float32) # 膨胀操作提取局部最大值 local_max = cv.dilate(response_map, kernel) # 仅保留等于局部最大值的像素 nms_map = np.where(response_map == local_max, response_map, 0) return nms_map def corners_of_square(img: np.ndarray, threshold=1.0) -> np.ndarray: img_cp = img.copy() if img_cp.ndim == 3: img_cp = cv.cvtColor(img_cp, cv.COLOR_BGR2GRAY).astype(np.float32) img_cp = img_cp.astype(np.float32) # 四个方向的角点滤波器(删除了重复定义的L和S) Z = np.array([ # 顶部角点 (Top) [ 1, 1, 1], [ 1, -1, -1], [ 1, -1, -1] ], dtype=np.float32) D = np.array([ # 右侧角点 (Right) [ 1, 1, 1], [-1, -1, 1], [-1, -1, 1] ], dtype=np.float32) L = np.array([ # 左侧角点 (Left) [ 1, -1, -1], [ 1, -1, -1], [ 1, 1, 1] ], dtype=np.float32) S = np.array([ # 底部角点 (Bottom) [-1, -1, 1], [-1, -1, 1], [ 1, 1, 1] ], dtype=np.float32) # 生成响应图 rZ = cv.filter2D(img_cp, cv.CV_32F, Z) rD = cv.filter2D(img_cp, cv.CV_32F, D) rL = cv.filter2D(img_cp, cv.CV_32F, L) rS = cv.filter2D(img_cp, cv.CV_32F, S) # 对每个响应图应用非极大值抑制 rZ_nms = non_max_suppression(rZ) rD_nms = non_max_suppression(rD) rL_nms = non_max_suppression(rL) rS_nms = non_max_suppression(rS) # 应用阈值过滤噪声 rZ_thresholded = np.where(rZ_nms > threshold, 1, 0) rD_thresholded = np.where(rD_nms > threshold, 1, 0) rL_thresholded = np.where(rL_nms > threshold, 1, 0) rS_thresholded = np.where(rS_nms > threshold, 1, 0) # 堆叠成4通道输出(Z、D、L、S顺序),每个通道的1表示该位置是对应方向的角点 out = np.stack([rZ_thresholded, rD_thresholded, rL_thresholded, rS_thresholded], axis=2) return out def visualize_corners(original_img, corner_map): """可视化角点:用不同颜色标记四个方向的角点""" vis_img = original_img.copy() if vis_img.ndim == 2: vis_img = cv.cvtColor(vis_img, cv.COLOR_GRAY2BGR) # 获取各通道的角点坐标 top_y, top_x = np.where(corner_map[..., 0] == 1) right_y, right_x = np.where(corner_map[..., 1] == 1) left_y, left_x = np.where(corner_map[..., 2] == 1) bottom_y, bottom_x = np.where(corner_map[..., 3] == 1) # 标记角点:顶部(红)、右侧(绿)、左侧(蓝)、底部(黄) for y, x in zip(top_y, top_x): cv.circle(vis_img, (x, y), 2, (0, 0, 255), -1) for y, x in zip(right_y, right_x): cv.circle(vis_img, (x, y), 2, (0, 255, 0), -1) for y, x in zip(left_y, left_x): cv.circle(vis_img, (x, y), 2, (255, 0, 0), -1) for y, x in zip(bottom_y, bottom_x): cv.circle(vis_img, (x, y), 2, (0, 255, 255), -1) return vis_img if __name__ == '__main__': rotated_squares_img = cv.imread(str(UTILS/"rotirani_kvadrati.png")).astype(np.float32) / 255 corners_map = corners_of_square(rotated_squares_img, threshold=1.5) # 可调整阈值 vis_img = visualize_corners(rotated_squares_img, corners_map) cv.imshow("Original Rotated Squares", rotated_squares_img) cv.imshow("Marked Corners", vis_img) cv.waitKey(0) cv.destroyAllWindows()
代码说明
- 新增的
non_max_suppression函数专门提取局部最大值,确保每个角点只保留一个像素点 visualize_corners函数用不同颜色直观标记四个方向的角点,方便你验证结果- 删掉了代码中重复定义的L和S滤波器,减少冗余
- 阈值参数可以根据你的测试图像调整,确保过滤掉噪声同时保留所有真实角点
如果你的正方形是旋转的,当前的滤波器可能对大角度旋转的角点响应不够好,后续可以考虑使用旋转不变的角点检测方法(比如Harris角点检测),但基于你现有的滤波器方案,上面的修改已经可以实现单像素标记的需求。
内容的提问来源于stack exchange,提问作者Marcel Majhenic
相关产品推荐
相关产品推荐

