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

如何基于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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 14:40:56