You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 05:25:36