如何用OpenCV Python精准定位LED弯折中心点?
弯折LED角度测量:中心点定位偏差修正方案
问题背景
我需要测量弯折LED的角度,目前通过凸包(convex hull)的凸性缺陷(convexity defects)实现了角度计算,但得到的中点偏离弯折中心。原图中LED为弯折形态,程序输出图里黑点是起点、红点是终点、蓝点是当前计算的中点,现需精准定位弯折中心点。
原代码
import cv2 import numpy as np from math import sqrt from collections import OrderedDict def findangle(x1,y1,x2,y2,x3,y3): ria = np.arctan2(y2 - y1, x2 - x1) - np.arctan2(y3 - y1, x3 - x1) if ria > 0: if ria < 3: webangle = int(np.abs(ria * 180 / np.pi)) elif ria > 3: webangle = int(np.abs(ria * 90 / np.pi)) elif ria < 0: if ria < -3: webangle = int(np.abs(ria * 90 / np.pi)) elif ria > -3: webangle = int(np.abs(ria * 180 / np.pi)) return webangle image = cv2.imread("cam/2022-09-27 10:01:57image.png") gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) _, thresh = cv2.threshold(gray, 240, 255, cv2.THRESH_BINARY) contours,hie= cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) selected_contour = max(contours, key=lambda x: cv2.contourArea(x)) # Draw Contour approx = cv2.approxPolyDP(selected_contour, 0.0035 * cv2.arcLength(selected_contour, True), True) for point in approx: cv2.drawContours(image, [point], 0, (0, 0, 255), 3) convexHull = cv2.convexHull(selected_contour,returnPoints=False) cv2.drawContours(image, cv2.convexHull(selected_contour), 0, (0, 255, 0), 3) convexHull[::-1].sort(axis=0) convexityDefects = cv2.convexityDefects(selected_contour, convexHull) start2,distance=[],[] for i in range(convexityDefects.shape[0]): s, e, f, d = convexityDefects[i, 0] start = tuple(selected_contour[s][0]) end = tuple(selected_contour[e][0]) far = tuple(selected_contour[f][0]) start2.append(start) cv2.circle(image, start, 2, (255, 0, 0), 3) cv2.line(image,start,end , (0, 255, 0), 3) distance.append(d) distance.sort(reverse=True) for i in range(convexityDefects.shape[0]): s, e, f, d = convexityDefects[i, 0] if distance[0]==d: defect={"s":s,"e":e,"f":f,"d":d} cv2.circle(image, selected_contour[defect.get("f")][0], 2, (255, 0, 0), 3) cv2.circle(image, selected_contour[defect.get("s")][0], 2, (0, 0, 0), 3) cv2.circle(image, selected_contour[defect.get("e")][0], 2, (0, 0, 255), 3) x1, y1 = selected_contour[defect.get("f")][0] x2, y2 = selected_contour[defect.get("e")][0] x3, y3 = selected_contour[defect.get("s")][0] cv2.line(image,(x1,y1),(x2,y2),(255,200,0),2) cv2.line(image,(x1,y1),(x3,y3),(255,200,0),2) cv2.putText(image, "Web Angle : " + str((findangle(x1,y1,x2,y2,x3,y3))), (50, 200), cv2.FONT_HERSHEY_SCRIPT_SIMPLEX, 1, (0,0,0),2,cv2.LINE_AA) cv2.imshow("frame",image) cv2.waitKey(0) cv2.destroyAllWindows()
修正方案
凸性缺陷返回的f点是轮廓上离凸包最远的点,并非弯折处的几何中心。可以通过多边形近似提取边+求边交点的方式精准定位中心点,具体步骤如下:
1. 优化预处理与轮廓提取
用自适应阈值替代固定阈值,减少光照不均带来的噪声干扰:
# 替换原阈值代码 thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2)
2. 精准多边形近似
调整approxPolyDP的精度参数,得到更简洁的多边形顶点,便于提取弯折处的两条边:
# 替换原approx代码 approx = cv2.approxPolyDP(selected_contour, 0.01 * cv2.arcLength(selected_contour, True), True)
3. 提取弯折边并计算交点
从近似多边形中找到弯折处的两条边,计算它们的交点作为真实中心点:
def line_intersection(line1, line2): """计算两条线段的交点""" x1, y1, x2, y2 = line1 x3, y3, x4, y4 = line2 denom = (x1 - x2)*(y3 - y4) - (y1 - y2)*(x3 - x4) if denom == 0: return None # 平行或重合 t_num = (x1 - x3)*(y3 - y4) - (y1 - y3)*(x3 - x4) u_num = (x1 - x3)*(y1 - y2) - (y1 - y3)*(x1 - x2) t = t_num / denom u = -u_num / denom if 0 <= t <= 1 and 0 <= u <= 1: x = x1 + t*(x2 - x1) y = y1 + t*(y2 - y1) return (int(x), int(y)) # 若线段无交点,延长线交点也可作为弯折中心 x = x1 + t*(x2 - x1) y = y1 + t*(y2 - y1) return (int(x), int(y)) # 从近似多边形中提取所有边 edges = [] for i in range(len(approx)): p1 = approx[i][0] p2 = approx[(i+1)%len(approx)][0] edges.append((p1[0], p1[1], p2[0], p2[1])) # 找到夹角最大的顶点,对应弯折处 max_angle = 0 bend_edges = None for i in range(len(approx)): p = approx[i][0] p_prev = approx[(i-1)%len(approx)][0] p_next = approx[(i+1)%len(approx)][0] # 计算两个相邻边的夹角 vec1 = p_prev - p vec2 = p_next - p norm1 = np.linalg.norm(vec1) norm2 = np.linalg.norm(vec2) if norm1 == 0 or norm2 == 0: continue angle = np.arccos(np.dot(vec1, vec2)/(norm1*norm2)) if angle > max_angle: max_angle = angle # 记录弯折处的两条边 bend_edges = ((p_prev[0], p_prev[1], p[0], p[1]), (p[0], p[1], p_next[0], p_next[1])) # 计算弯折中心点 real_center = None if bend_edges: real_center = line_intersection(bend_edges[0], bend_edges[1])
4. 优化角度计算函数
简化原角度计算逻辑,避免复杂的分支判断:
def findangle(x1,y1,x2,y2,x3,y3): """计算以(x1,y1)为顶点的夹角""" vec1 = np.array([x2 - x1, y2 - y1]) vec2 = np.array([x3 - x1, y3 - y1]) norm1 = np.linalg.norm(vec1) norm2 = np.linalg.norm(vec2) if norm1 == 0 or norm2 == 0: return 0 dot_product = np.dot(vec1, vec2) angle_rad = np.arccos(np.clip(dot_product/(norm1*norm2), -1.0, 1.0)) angle_deg = int(np.degrees(angle_rad)) # 返回内角(小于等于180度) return angle_deg if angle_deg <= 180 else 360 - angle_deg
5. 替换中心点并可视化
将原代码中用凸性缺陷f点的部分替换为计算出的真实中心点,然后绘制结果:
# 替换原凸性缺陷中心点相关代码 if real_center: x1, y1 = real_center # 找到LED的两个端点(近似多边形中距离最远的两个点) points = [tuple(p[0]) for p in approx] max_dist = 0 end_points = None for i in range(len(points)): for j in range(i+1, len(points)): dist = np.linalg.norm(np.array(points[i]) - np.array(points[j])) if dist > max_dist: max_dist = dist end_points = (points[i], points[j]) if end_points: x2, y2 = end_points[0] x3, y3 = end_points[1] # 绘制标记与角度 cv2.circle(image, real_center, 3, (0, 255, 255), -1) # 黄色标记真实中心点 cv2.circle(image, end_points[0], 3, (0, 0, 0), -1) # 黑点起点 cv2.circle(image, end_points[1], 3, (0, 0, 255), -1) # 红点终点 cv2.line(image, real_center, end_points[0], (255,200,0),2) cv2.line(image, real_center, end_points[1], (255,200,0),2) angle = findangle(x1,y1,x2,y2,x3,y3) cv2.putText(image, "LED Angle : " + str(angle), (50, 200), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,0),2,cv2.LINE_AA)
完整修改后代码
import cv2 import numpy as np def line_intersection(line1, line2): x1, y1, x2, y2 = line1 x3, y3, x4, y4 = line2 denom = (x1 - x2)*(y3 - y4) - (y1 - y2)*(x3 - x4) if denom == 0: return None t_num = (x1 - x3)*(y3 - y4) - (y1 - y3)*(x3 - x4) u_num = (x1 - x3)*(y1 - y2) - (y1 - y3)*(x1 - x2) t = t_num / denom u = -u_num / denom if 0 <= t <= 1 and 0 <= u <= 1: x = x1 + t*(x2 - x1) y = y1 + t*(y2 - y1) return (int(x), int(y)) x = x1 + t*(x2 - x1) y = y1 + t*(y2 - y1) return (int(x), int(y)) def findangle(x1,y1,x2,y2,x3,y3): vec1 = np.array([x2 - x1, y2 - y1]) vec2 = np.array([x3 - x1, y3 - y1]) norm1 = np.linalg.norm(vec1) norm2 = np.linalg.norm(vec2) if norm1 == 0 or norm2 == 0: return 0 dot_product = np.dot(vec1, vec2) angle_rad = np.arccos(np.clip(dot_product/(norm1*norm2), -1.0, 1.0)) angle_deg = int(np.degrees(angle_rad)) return angle_deg if angle_deg <= 180 else 360 - angle_deg image = cv2.imread("cam/2022-09-27 10:01:57image.png") gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 自适应阈值 thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2) contours,hie= cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) selected_contour = max(contours, key=lambda x: cv2.contourArea(x)) # 精准多边形近似 approx = cv2.approxPolyDP(selected_contour, 0.01 * cv2.arcLength(selected_contour, True), True) for point in approx: cv2.drawContours(image, [point], 0, (0, 0, 255), 3) # 提取弯折边并计算中心点 edges = [] for i in range(len(approx)): p1 = approx[i][0] p2 = approx[(i+1)%len(approx)][0] edges.append((p1[0], p1[1], p2[0], p2[1])) max_angle = 0 bend_edges = None for i in range(len(approx)): p = approx[i][0] p_prev = approx[(i-1)%len(approx)][0] p_next = approx[(i+1)%len(approx)][0] vec1 = p_prev - p vec2 = p_next - p norm1 = np.linalg.norm(vec1) norm2 = np.linalg.norm(vec2) if norm1 == 0 or norm2 == 0: continue angle = np.arccos(np.dot(vec1, vec2)/(norm1*norm2)) if angle > max_angle: max_angle = angle bend_edges = ((p_prev[0], p_prev[1], p[0], p[1]), (p[0], p[1], p_next[0], p_next[1])) real_center = None if bend_edges: real_center = line_intersection(bend_edges[0], bend_edges[1]) # 绘制结果 if real_center: x1, y1 = real_center points = [tuple(p[0]) for p in approx] max_dist = 0 end_points = None for i in range(len(points)): for j in range(i+1, len(points)): dist = np.linalg.norm(np.array(points[i]) - np.array(points[j])) if dist > max_dist: max_dist = dist end_points = (points[i], points[j]) if end_points: x2, y2 = end_points[0] x3, y3 = end_points[1] cv2.circle(image, real_center, 3, (0, 255, 255), -1) cv2.circle(image, end_points[0], 3, (0, 0, 0), -1) cv2.circle(image, end_points[1], 3, (0, 0, 255), -1) cv2.line(image, real_center, end_points[0], (255,200,0),2) cv2.line
相关产品推荐
相关产品推荐

