如何用OpenCV识别水滴状鱼形轮廓的圆滑与尖锐端点坐标?
水滴状轮廓端点检测(鱼头尾识别)技术思路
针对你从顶拍水族箱得到的水滴状鱼轮廓,分享几个直接落地的技术思路:
1. 轮廓曲率分析筛选端点
水滴状轮廓的尖锐端(尾部)曲率远大于圆滑端(头部),可以通过计算轮廓点的曲率来区分:
- 步骤:
- 从二值图中提取目标轮廓(用
cv2.findContours,取面积最大的轮廓确保是单条鱼) - 遍历轮廓的每个点,取相邻三个点计算夹角:假设点序列为
p0, p1, p2,计算向量v1 = p1-p0、v2 = p2-p1,用向量点积求夹角余弦值,余弦值越小,夹角越尖锐 - 筛选余弦值最小的点作为尖锐端(尾部),余弦值最大(接近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. 最小外接椭圆结合曲率验证
水滴状轮廓的最小外接椭圆长轴两端点基本对应头尾,再用曲率区分:
- 步骤:
- 用
cv2.fitEllipse拟合轮廓的最小外接椭圆,得到长轴的两个端点 - 分别计算两个端点附近轮廓点的曲率,曲率大的是尖锐端,小的是圆滑端
- 用
- 代码片段:
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. 骨架提取+端点检测
对二值图做骨架提取后,水滴状的骨架只会有两个端点,再通过周边区域区分:
- 步骤:
- 用
cv2.ximgproc.thinning对二值图做骨架提取(需要安装opencv-contrib-python) - 遍历骨架图像的像素,找到仅与一个其他骨架像素相连的点(端点)
- 比较两个端点周边的图像面积:圆滑端(头部)周边的白色区域面积更大,尖锐端(尾部)周边面积小
- 用
- 代码片段:
# 骨架提取 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
相关产品推荐
相关产品推荐

