使用OpenCV检测红色背景上哑光白色串状小球的问题求助
串状小球检测优化需求
我并非程序员,而是一名物理学家,需要检测如下串状排列的小球:
此前有人协助编写了如下检测算法:
import cv2 import numpy as np def find_ball_positions(file_name): centers = [] rrs = [] # Read image img = cv2.imread(file_name) # Convert to HSV and keep the hue channel hue = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)[:, :, 0] # beads are red but on the blue side, so very bright hues # so threshold hue at 165 (out of 180) thresh = cv2.threshold(hue, 100, 255, cv2.THRESH_BINARY)[1] # apply morphology to fill beads and remove excess spots kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) morph = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel) kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (6, 6)) morph = cv2.morphologyEx(morph, cv2.MORPH_CLOSE, kernel) # get contours and enclosing circles contours = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) contours = contours[0] if len(contours) == 2 else contours[1] circles = img.copy() num_circles = len(contours) rrs = np.zeros(num_circles, dtype=int) ave = 0 for ii, cntr in enumerate(contours): center, radius = cv2.minEnclosingCircle(cntr) cx = int(round(center[0])) cy = int(round(center[1])) rr = int(round(radius)) centers.append((cx, cy)) rrs[ii] = rr # draw enclosing circle over beads cv2.circle(circles, (cx, cy), rr, (0, 255, 0), 2) # cumulate radii for average ave = ave + radius # print average radius ave_radius = ave / num_circles print("average radius:", ave_radius) print("number of circles:", num_circles) # save results cv2.imwrite('beads_thresh.jpg', thresh) cv2.imwrite('beads_morph.jpg', morph) cv2.imwrite('beads_circles.jpg', circles) cv2.imshow('thresh', thresh) cv2.imshow('morph', morph) cv2.imshow('circles', circles) cv2.waitKey(0) cv2.destroyAllWindows() return { "centers": centers, "rrs": rrs, "image":img }
原有算法受小球反光影响检测效果较差,失效示例如下:
未选择软件层面优化反光问题,改为将所有小球喷涂为哑光白色,现在遇到新问题:受背景噪声干扰,无法在红色背景上正常检测白色小球,相关样本如下:

优化方案
针对哑光白珠+红色背景的场景,对原有算法做三处核心修改即可实现稳定检测:
- 调整HSV阈值逻辑:放弃原有的单H通道阈值,改用三通道组合筛选白色区域。白色的HSV特征为饱和度低、亮度高,红色背景的饱和度远高于白色,阈值范围设置为
lower_white = (0, 0, 200)、upper_white = (180, 30, 255)即可完美分离白珠和红色背景。 - 增加圆形度过滤:通过
4 * np.pi * cv2.contourArea(cnt) / (cv2.arcLength(cnt, True)**2)计算轮廓的圆形度,仅保留圆形度大于0.8的轮廓,过滤背景不规则噪声。 - 增加半径范围过滤:统计所有候选圆的半径平均值,仅保留半径与平均值差值小于20%的结果,排除尺寸异常的误检目标。
优化后完整代码
import cv2 import numpy as np def find_ball_positions(file_name): centers = [] rrs = [] img = cv2.imread(file_name) hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # 白珠阈值范围,可根据实际拍摄亮度微调V通道的下限200 lower_white = (0, 0, 200) upper_white = (180, 30, 255) thresh = cv2.inRange(hsv, lower_white, upper_white) # 形态学操作保留原有逻辑 kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) morph = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel) kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (6, 6)) morph = cv2.morphologyEx(morph, cv2.MORPH_CLOSE, kernel) contours = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) contours = contours[0] if len(contours) == 2 else contours[1] circles = img.copy() # 第一轮筛选:圆形度过滤 candidate_circles = [] for cnt in contours: area = cv2.contourArea(cnt) if area < 10: # 过滤极小杂点 continue perimeter = cv2.arcLength(cnt, True) circularity = 4 * np.pi * area / (perimeter ** 2) if circularity > 0.8: center, radius = cv2.minEnclosingCircle(cnt) candidate_circles.append((center, radius)) # 第二轮筛选:半径过滤 if len(candidate_circles) == 0: print("未检测到小球") return {"centers": [], "rrs": [], "image": img} radii = [r for (c, r) in candidate_circles] avg_r = np.mean(radii) valid_circles = [(c, r) for (c, r) in candidate_circles if abs(r - avg_r) < avg_r * 0.2] # 输出结果 num_circles = len(valid_circles) rrs = np.zeros(num_circles, dtype=int) for ii, (center, radius) in enumerate(valid_circles): cx = int(round(center[0])) cy = int(round(center[1])) rr = int(round(radius)) centers.append((cx, cy)) rrs[ii] = rr cv2.circle(circles, (cx, cy), rr, (0, 255, 0), 2) ave_radius = np.mean(rrs) print("average radius:", ave_radius) print("number of circles:", num_circles) cv2.imwrite('beads_thresh.jpg', thresh) cv2.imwrite('beads_morph.jpg', morph) cv2.imwrite('beads_circles.jpg', circles) cv2.imshow('thresh', thresh) cv2.imshow('morph', morph) cv2.imshow('circles', circles) cv2.waitKey(0) cv2.destroyAllWindows() return { "centers": centers, "rrs": rrs, "image":img }
如果实际拍摄亮度有差异,微调阈值里的V通道下限(默认200)即可适配。
内容的提问来源于stack exchange,提问作者BlueScreen
相关产品推荐
相关产品推荐

