如何基于OpenCV优化墨西哥比索纸币颜色识别与计数功能?
墨西哥比索纸币计数器优化需求
我正在用Python+OpenCV开发基于颜色识别的墨西哥比索纸币计数器,目前系统能偶尔识别纸币颜色,但存在两个核心问题:
- 单张纸币会被重复计数2-5次,原因是纸币内部有重复色块
- 预设颜色值经常检测不到纸币
现有代码
import cv2 import numpy as np import imutils import time #========================================================================= bill_colors = { (67, 86, 101): '20pesos', (140, 58, 51): '50pesos', (150, 128, 119): '100pesos' } def ContourCalculate(image): hsvImg = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) detected_bills = set() bill_counts = {bill_type: 0 for bill_type in bill_colors.values()} for color, bill_type in bill_colors.items(): for color_range in get_color_ranges(color): mask = cv2.inRange(hsvImg, np.array(color_range[0]), np.array(color_range[1])) contours = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) contours = imutils.grab_contours(contours) for contour in contours: area = cv2.contourArea(contour) if area > 1600: x, y, w, h = cv2.boundingRect(contour) bill_box = (x, y, x + w, y + h) if all(bill_box[i] not in detected_bills for i in range(4)): detected_bills.update(range(x, x + w)) bill_counts[bill_type] += 1 return bill_counts def get_color_ranges(color): # Define multiple color ranges for each bill type ranges = [] ranges.append((np.array(color) - np.array([10, 50, 50]), np.array(color) + np.array([10, 50, 50]))) ranges.append((np.array(color) - np.array([20, 100, 100]), np.array(color) + np.array([20, 100, 100]))) ranges.append((np.array(color) - np.array([30, 150, 150]), np.array(color) + np.array([30, 150, 150]))) return ranges cap = cv2.VideoCapture(0) start_time = time.time() while True: success, img = cap.read() img = cv2.flip(img, 1) imgS = img.copy() # Check if 2 seconds have passed if time.time() - start_time >= 2: bill_counts = ContourCalculate(imgS) for bill_type, count in bill_counts.items(): print(f'{bill_type}: {count}') start_time = time.time() # Reset the timer cv2.imshow("image", img) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()
目标纸币

优化方案
一、解决单张纸币重复计数问题
原代码用x轴范围判断重复的逻辑存在缺陷,纸币内部色块的x范围会和纸币本体重叠,导致误判。改用非极大值抑制(NMS)+**交并比(IoU)**过滤重复框,核心逻辑是:同一纸币的所有色块轮廓重叠度极高,通过IoU判断是否属于同一纸币,只保留唯一有效框。
二、提升颜色检测精度
原固定偏移的颜色范围没有考虑HSV通道特性(H色相范围0-179,S/V是饱和度/明度0-255),且单一点的颜色值易受光照干扰。优化方向:
- 针对每种纸币手动采集精准HSV区间,而非固定偏移
- 增加预处理:高斯模糊降噪、形态学操作填充色块空隙
- 调整通道偏移:H通道偏移控制在±15以内,S/V可适当放宽至±50
修改后的完整代码
import cv2 import numpy as np import imutils import time # 针对每种纸币手动调整的精准HSV颜色区间(可根据实际光照微调) bill_color_ranges = { '20pesos': [ (np.array([57, 36, 51]), np.array([77, 136, 151])), (np.array([47, 0, 0]), np.array([87, 200, 200])) ], '50pesos': [ (np.array([130, 8, 1]), np.array([150, 108, 101])), (np.array([120, 0, 0]), np.array([160, 158, 151])) ], '100pesos': [ (np.array([140, 78, 69]), np.array([160, 178, 169])), (np.array([130, 28, 19]), np.array([170, 228, 219])) ] } def calculate_iou(box1, box2): # 计算两个边界框的交并比 x1, y1, x2, y2 = box1 x3, y3, x4, y4 = box2 # 计算交集区域 inter_x1 = max(x1, x3) inter_y1 = max(y1, y3) inter_x2 = min(x2, x4) inter_y2 = min(y2, y4) inter_area = max(0, inter_x2 - inter_x1) * max(0, inter_y2 - inter_y1) box1_area = (x2 - x1) * (y2 - y1) box2_area = (x4 - x3) * (y4 - y3) return inter_area / (box1_area + box2_area - inter_area) def non_max_suppression(boxes, iou_threshold=0.3): # 非极大值抑制,过滤重叠框 if not boxes: return [] # 按轮廓面积从大到小排序 boxes = sorted(boxes, key=lambda x: (x[2]-x[0])*(x[3]-x[1]), reverse=True) keep_boxes = [] while boxes: current_box = boxes.pop(0) keep = True for box in keep_boxes: if calculate_iou(current_box, box) > iou_threshold: keep = False break if keep: keep_boxes.append(current_box) return keep_boxes def ContourCalculate(image): # 预处理:高斯模糊降噪 blurred = cv2.GaussianBlur(image, (5,5), 0) hsvImg = cv2.cvtColor(blurred, cv2.COLOR_BGR2HSV) bill_counts = {bill_type: 0 for bill_type in bill_color_ranges.keys()} for bill_type, color_ranges in bill_color_ranges.items(): all_boxes = [] for color_range in color_ranges: mask = cv2.inRange(hsvImg, color_range[0], color_range[1]) # 形态学闭操作:填充色块内部空隙 kernel = np.ones((3,3), np.uint8) mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) contours = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) contours = imutils.grab_contours(contours) for contour in contours: area = cv2.contourArea(contour) # 根据实际纸币大小调整面积阈值 if area > 2000: x, y, w, h = cv2.boundingRect(contour) # 过滤宽高比不符合纸币的框(纸币宽高比约2:1) aspect_ratio = w / h if (1.8 < aspect_ratio < 2.5) or (0.4 < aspect_ratio < 0.55): all_boxes.append((x, y, x+w, y+h)) # 对当前纸币类型的所有框做非极大值抑制 unique_boxes = non_max_suppression(all_boxes) bill_counts[bill_type] = len(unique_boxes) return bill_counts cap = cv2.VideoCapture(0) start_time = time.time() while True: success, img = cap.read() if not success: break img = cv2.flip(img, 1) imgS = img.copy() if time.time() - start_time >= 2: bill_counts = ContourCalculate(imgS) for bill_type, count in bill_counts.items(): print(f'{bill_type}: {count}') start_time = time.time() cv2.imshow("image", img) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()
额外优化建议
- 固定光照:使用补光灯,避免强光或阴影干扰颜色识别
- 颜色校准:拍摄纸币样本,用OpenCV颜色拾取工具获取精准HSV范围
- 尺寸过滤:根据实际纸币的像素大小,设置更精准的面积和宽高比阈值
内容的提问来源于stack exchange,提问作者Sahm
相关产品推荐
相关产品推荐

