import cv2 import numpy as np # ======================== # 配置 # ======================== IMAGE_PATH = "22.png" CANNY_LOW = 60 CANNY_HIGH = 150 # 判定“近似水平”的阈值 MAX_SLOPE = 0.2 # |dy/dx| < 0.2 认为是水平 # ======================== def main(): img = cv2.imread(IMAGE_PATH) if img is None: raise RuntimeError("图片读取失败") h, w = img.shape[:2] # 1. 灰度 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 2. 模糊(非常关键,压制粗糙纹理) blur = cv2.GaussianBlur(gray, (7, 7), 0) # 3. Canny edges = cv2.Canny(blur, CANNY_LOW, CANNY_HIGH) # 4. 找轮廓(只用来拿点) contours, _ = cv2.findContours( edges, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE ) horizontal_points = [] # 5. 筛选“近似水平”的边缘点 for cnt in contours: if len(cnt) < 50: continue pts = cnt.squeeze() if pts.ndim != 2: continue x = pts[:, 0] y = pts[:, 1] dx = x.max() - x.min() dy = y.max() - y.min() if dx < 100: # 太短的不要 continue slope = dy / (dx + 1e-6) if slope < MAX_SLOPE: horizontal_points.append(pts) if not horizontal_points: print("❌ 没找到合适的水平边") return # 6. 合并所有候选点 all_pts = np.vstack(horizontal_points) # 7. 选“最靠上的那一条” # 方法:按 y 排序,取 y 最小的一部分 all_pts = all_pts[all_pts[:, 1].argsort()] # 取前 20% 作为“上沿候选” top_n = int(len(all_pts) * 0.2) top_edge_pts = all_pts[:top_n] # 8. 用最小二乘拟合直线 y = ax + b xs = top_edge_pts[:, 0] ys = top_edge_pts[:, 1] a, b = np.polyfit(xs, ys, 1) # 9. 画线 x0, x1 = 0, w - 1 y0 = int(a * x0 + b) y1 = int(a * x1 + b) vis = img.copy() cv2.line(vis, (x0, y0), (x1, y1), (0, 0, 255), 2) # 可视化点 for p in top_edge_pts[::20]: cv2.circle(vis, tuple(p), 1, (0, 255, 0), -1) cv2.imshow("edges", edges) cv2.imshow("top plate edge", vis) cv2.waitKey(0) cv2.destroyAllWindows() if __name__ == "__main__": main()