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

如何用OpenCV识别水滴状鱼形轮廓的圆滑与尖锐端点坐标?

水滴状轮廓端点检测(鱼头尾识别)技术思路

针对你从顶拍水族箱得到的水滴状鱼轮廓,分享几个直接落地的技术思路:

1. 轮廓曲率分析筛选端点

水滴状轮廓的尖锐端(尾部)曲率远大于圆滑端(头部),可以通过计算轮廓点的曲率来区分:

  • 步骤:
    1. 从二值图中提取目标轮廓(用cv2.findContours,取面积最大的轮廓确保是单条鱼)
    2. 遍历轮廓的每个点,取相邻三个点计算夹角:假设点序列为p0, p1, p2,计算向量v1 = p1-p0、v2 = p2-p1,用向量点积求夹角余弦值,余弦值越小,夹角越尖锐
    3. 筛选余弦值最小的点作为尖锐端(尾部),余弦值最大(接近1)的点作为圆滑端(头部)
  • 代码片段:
import cv2
import numpy as np

# 假设已经得到二值化图像binary_img
contours, _ = cv2.findContours(binary_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
fish_contour = max(contours, key=cv2.contourArea)

curvatures = []
for i in range(len(fish_contour)):
    p0 = fish_contour[i-1][0]
    p1 = fish_contour[i][0]
    p2 = fish_contour[(i+1)%len(fish_contour)][0]
    
    v1 = p0 - p1
    v2 = p2 - p1
    dot = np.dot(v1, v2)
    norm = np.linalg.norm(v1) * np.linalg.norm(v2)
    cos_angle = dot / norm if norm != 0 else 1.0
    curvatures.append((cos_angle, p1))

# 尖锐端:余弦值最小(夹角最尖)
sharp_point = min(curvatures, key=lambda x: x[0])[1]
# 圆滑端:余弦值最大(夹角最平缓)
smooth_point = max(curvatures, key=lambda x: x[0])[1]

2. 最小外接椭圆结合曲率验证

水滴状轮廓的最小外接椭圆长轴两端点基本对应头尾,再用曲率区分:

  • 步骤:
    1. 用cv2.fitEllipse拟合轮廓的最小外接椭圆,得到长轴的两个端点
    2. 分别计算两个端点附近轮廓点的曲率,曲率大的是尖锐端,小的是圆滑端
  • 代码片段:
ellipse = cv2.fitEllipse(fish_contour)
center, axes, angle = ellipse
# 计算长轴的两个端点
axis_length = max(axes)
x1 = int(center[0] + axis_length/2 * np.cos(np.radians(angle)))
y1 = int(center[1] + axis_length/2 * np.sin(np.radians(angle)))
x2 = int(center[0] - axis_length/2 * np.cos(np.radians(angle)))
y2 = int(center[1] - axis_length/2 * np.sin(np.radians(angle)))
endpoints = [(x1,y1), (x2,y2)]

# 计算每个端点附近的平均曲率
def get_avg_curvature(contour, point):
    distances = [np.linalg.norm(p[0]-point) for p in contour]
    idx = np.argmin(distances)
    total_cos = 0
    for offset in range(-2,3):
        i = (idx + offset) % len(contour)
        p0 = contour[i-1][0]
        p1 = contour[i][0]
        p2 = contour[(i+1)%len(contour)][0]
        v1 = p0 - p1
        v2 = p2 - p1
        dot = np.dot(v1, v2)
        norm = np.linalg.norm(v1) * np.linalg.norm(v2)
        total_cos += dot / norm if norm !=0 else 1.0
    return total_cos /5

curv1 = get_avg_curvature(fish_contour, endpoints[0])
curv2 = get_avg_curvature(fish_contour, endpoints[1])
sharp_point = endpoints[0] if curv1 < curv2 else endpoints[1]
smooth_point = endpoints[1] if curv1 < curv2 else endpoints[0]

3. 骨架提取+端点检测

对二值图做骨架提取后,水滴状的骨架只会有两个端点,再通过周边区域区分:

  • 步骤:
    1. 用cv2.ximgproc.thinning对二值图做骨架提取(需要安装opencv-contrib-python)
    2. 遍历骨架图像的像素,找到仅与一个其他骨架像素相连的点(端点)
    3. 比较两个端点周边的图像面积:圆滑端(头部)周边的白色区域面积更大,尖锐端(尾部)周边面积小
  • 代码片段:
# 骨架提取
skeleton = cv2.ximgproc.thinning(binary_img)

# 找骨架端点
endpoints = []
h, w = skeleton.shape
for y in range(1, h-1):
    for x in range(1, w-1):
        if skeleton[y][x] == 255:
            # 统计8邻域内的白色像素数量
            neighbors = np.sum(skeleton[y-1:y+2, x-1:x+2])//255 -1
            if neighbors == 1:
                endpoints.append((x,y))

# 区分头尾:计算端点周围指定范围的白色面积
def get_surround_area(binary_img, point, radius=10):
    x, y = point
    x_min = max(0, x-radius)
    x_max = min(binary_img.shape[1], x+radius)
    y_min = max(0, y-radius)
    y_max = min(binary_img.shape[0], y+radius)
    area = np.sum(binary_img[y_min:y_max, x_min:x_max])//255
    return area

area1 = get_surround_area(binary_img, endpoints[0])
area2 = get_surround_area(binary_img, endpoints[1])
smooth_point = endpoints[0] if area1 > area2 else endpoints[1]
sharp_point = endpoints[1] if area1 > area2 else endpoints[0]

以上思路都可以直接适配你的场景,优先试试曲率分析的方法,计算量小且精度稳定。

内容的提问来源于stack exchange,提问作者Marc

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 12:50:58