基于树结构的鱼类头尾最长距离提取:如何规避鱼鳍干扰?
鱼类最长线提取(规避鱼鳍干扰)
我正尝试用树结构实现鱼类最长线提取,目标是规避鱼鳍干扰,找到鱼头到鱼尾的最长距离。目前用最大距离法定位的两点分别在鱼鳍和尾部,不符合需求——我需要的是鱼头和鱼尾的两点来计算最长线。已经实现了骨架提取,但鱼鳍导致骨架中心线存在额外分支,现有实现代码如下:
binary_fish = cv2.imread('/content/fish4-masked.jpg', cv2.IMREAD_GRAYSCALE) # Step 1: Preprocessing # Apply erosion and dilation to the binary fish image kernel = np.ones((3, 3), np.uint8) binary_fish = cv2.erode(binary_fish, kernel, iterations=1) binary_fish = cv2.dilate(binary_fish, kernel, iterations=1) # Step 2: Threshold to create a binary image _, binary_fish = cv2.threshold(binary_fish, 128, 255, cv2.THRESH_BINARY) # Step 3: Skeletonize the binary fish image skeleton = morphology.skeletonize(binary_fish > 0) # Step 4: Find the centerline from the skeleton skeleton = skeleton.astype(np.uint8) * 255 # Find the contours of the skeleton contours, _ = cv2.findContours(skeleton, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # Extract the largest contour (assuming it's the centerline) centerline = max(contours, key=cv2.contourArea) # Create a blank canvas height, width = binary_fish.shape centerline_image = np.zeros((height, width), dtype=np.uint8) # Draw the centerline on the canvas cv2.drawContours(centerline_image, [centerline], -1, 255, 1) # Find the coordinates of the points on the centerline points = centerline.squeeze(axis=1) max_distance = 0 farthest_points = (0, 0), (0, 0) # Calculate pairwise distances and find the two farthest points for i in range(len(points)): for j in range(i + 1, len(points)): x1, y1 = points[i] x2, y2 = points[j] distance = math.sqrt((x2 - x1)**2 + (y2 - y1)**2) if distance > max_distance: max_distance = distance farthest_points = (x1, y1), (x2, y2) farthest_point1, farthest_point2 = farthest_points radius = 5 # Radius of the circle color = (0, 255, 0) # Green color (you can choose a different color) thickness = -1 # Fill the circle # Draw the first farthest point cv2.circle(original_fish, farthest_point1, radius, color, thickness) # Draw the second farthest point cv2.circle(original_fish, farthest_point2, radius, color, thickness)
效果图描述
- 骨架提取结果:生成的鱼类骨架包含鱼鳍带来的额外分支,干扰了主轴线识别
- 两点定位结果:当前算法标记的最远点分别落在鱼鳍和尾部,未对应鱼头与鱼尾
解决思路与改进方案
核心问题是鱼鳍分支干扰骨架主线,导致最远点计算偏离目标。以下是两种直接可行的优化方向:
1. 修剪骨架分支(去除鱼鳍干扰)
骨架是树状结构,鱼鳍属于短侧枝,可通过节点度数(邻域非零点数)识别分叉点并修剪:
- 主骨架节点度数多为2(中间点)或1(端点)
- 鱼鳍分叉处节点度数≥3,从分叉点递归修剪短分支
添加修剪函数并插入原有流程:
import numpy as np import cv2 from skimage import morphology def prune_skeleton(skeleton): skel = skeleton.copy() // 255 coords = np.argwhere(skel == 1) for (y, x) in coords: neighbors = skel[y-1:y+2, x-1:x+2] degree = np.sum(neighbors) - 1 # 减去自身点 if degree > 2: prune_branch(skel, y, x) return (skel * 255).astype(np.uint8) def prune_branch(skel, y, x): skel[y, x] = 0 for dy in [-1, 0, 1]: for dx in [-1, 0, 1]: if dy == 0 and dx == 0: continue ny, nx = y + dy, x + dx if 0 <= ny < skel.shape[0] and 0 <= nx < skel.shape[1]: if skel[ny, nx] == 1: neighbors = skel[ny-1:ny+2, nx-1:nx+2] degree = np.sum(neighbors) - 1 if degree <= 2: prune_branch(skel, ny, nx) # 在原有代码Step3后插入: skeleton = prune_skeleton(skeleton)
2. 直接提取骨架端点(替代暴力最远点计算)
修剪后的骨架为单条主线,只需提取度数为1的端点,即为鱼头和鱼尾:
def find_skeleton_endpoints(skeleton): skel = skeleton // 255 coords = np.argwhere(skel == 1) endpoints = [] for (y, x) in coords: neighbors = skel[y-1:y+2, x-1:x+2] degree = np.sum(neighbors) - 1 if degree == 1: endpoints.append((x, y)) # 转换为OpenCV的(x,y)格式 return endpoints # 替换原有最远点计算逻辑: endpoints = find_skeleton_endpoints(skeleton) if len(endpoints) >= 2: farthest_point1, farthest_point2 = endpoints[0], endpoints[1] max_distance = np.linalg.norm(np.array(farthest_point1) - np.array(farthest_point2))
额外优化建议
- 预处理阶段:先计算鱼类的最小外接矩形,将图像旋转至水平方向,减少骨架分支的判断难度
- 噪声过滤:骨架提取前,用
cv2.morphologyEx做开运算,去除小噪声区域
内容的提问来源于stack exchange,提问作者repo_twei
相关产品推荐
相关产品推荐

