如何用Python将多贴纸图片中的SKU与序列号条码按贴纸分组
问题描述
目标
使用Python实现产品条码自动扫描并入物流系统。
场景
每个产品的单张贴纸包含两个条码:SKU(标识产品线)、序列号(同产品线内产品的唯一ID)。图片中可能存在多组不同产品线的贴纸,每组贴纸对应唯一序列号。
现有进度
已通过pyzbar与cv2库实现图片中多个条码的扫描,代码如下:
from pyzbar.pyzbar import decode, ZBarSymbol import cv2 testing_image_readin = cv2.imread(testing_image_path) detected_barcodes = decode(testing_image_readin, symbols=[ZBarSymbol.CODE128, ZBarSymbol.EAN13]) if not detected_barcodes: print("Barcode Not Detected or your barcode is blank/corrupted!") else: for barcode in detected_barcodes: # Locate the barcode position in image (x, y, w, h) = barcode.rect cv2.rectangle(testing_image_readin, (x - 10, y - 10), (x + w + 10, y + h + 10), (255, 0, 0), 2) if barcode.data != "": # Print the barcode data print(barcode.data) print(barcode.type)
当前问题
无法将识别出的SKU与序列号条码按所属单张贴纸进行分组,需实现分组方法与思路。
示例说明
- 单张贴纸:包含一个CODE128类型的序列号、一个EAN13类型的SKU
- 对应程序输出:
b'07FFD58D47189877' CODE128 b'0871828002084' EAN13
- 多张贴纸俯视:图片中包含多组上述贴纸,需将每组的SKU与序列号配对。
解决方案
核心思路
利用条码在图片中的空间位置关系分组:同一张贴纸的两个条码距离更近,且大概率处于同一区域。具体步骤:
- 为每个条码计算中心坐标
- 计算条码间的欧氏距离,将距离小于阈值的条码归为一组
- 结合条码类型(SKU为EAN13,序列号为CODE128),每组内自动配对SKU和序列号
实现代码
from pyzbar.pyzbar import decode, ZBarSymbol import cv2 import math def calculate_center(x, y, w, h): """计算条码矩形的中心坐标""" return (x + w//2, y + h//2) def distance_between_points(p1, p2): """计算两点间欧氏距离""" return math.hypot(p2[0] - p1[0], p2[1] - p1[1]) def group_barcodes(detected_barcodes, distance_threshold=100): """将同一张贴纸的条码分组""" groups = [] # 按y轴中心排序,优化分组效率 barcodes_sorted = sorted(detected_barcodes, key=lambda b: calculate_center(*b.rect)[1]) for barcode in barcodes_sorted: added = False curr_center = calculate_center(*barcode.rect) # 遍历已有组,判断是否属于同一组 for group in groups: group_center = calculate_center(*group[0].rect) if distance_between_points(curr_center, group_center) < distance_threshold: group.append(barcode) added = True break if not added: groups.append([barcode]) # 过滤掉单个条码的组(识别不全的贴纸) return [g for g in groups if len(g) == 2] def pair_sku_serial(group): """分组内配对SKU(EAN13)和序列号(CODE128)""" sku = None serial = None for barcode in group: if barcode.type == 'EAN13': sku = barcode.data.decode('utf-8') elif barcode.type == 'CODE128': serial = barcode.data.decode('utf-8') return {'SKU': sku, 'Serial': serial} # 主流程 testing_image_path = "your_image_path.jpg" testing_image_readin = cv2.imread(testing_image_path) detected_barcodes = decode(testing_image_readin, symbols=[ZBarSymbol.CODE128, ZBarSymbol.EAN13]) if not detected_barcodes: print("未检测到条码,或条码为空/损坏!") else: barcode_groups = group_barcodes(detected_barcodes) print("分组结果:") for idx, group in enumerate(barcode_groups, 1): pair = pair_sku_serial(group) print(f"第{idx}组:SKU={pair['SKU']}, 序列号={pair['Serial']}") # 可视化分组(不同组用不同颜色框) color = (0, 255*idx//len(barcode_groups), 0) for barcode in group: x, y, w, h = barcode.rect cv2.rectangle(testing_image_readin, (x-10, y-10), (x+w+10, y+h+10), color, 2) cv2.imshow("Grouped Barcodes", testing_image_readin) cv2.waitKey(0) cv2.destroyAllWindows()
关键说明
- 距离阈值调整:
distance_threshold需根据实际图片尺寸设置,建议先测试单张贴纸的两个条码中心距离,阈值设为该距离的1.2倍左右。 - 条码类型适配:若SKU和序列号的条码类型有变动,直接修改
pair_sku_serial函数的判断逻辑即可。 - 异常处理:过滤单个条码的组,避免无效数据;若分组内缺少SKU/序列号,可添加日志提醒人工核对。
内容的提问来源于stack exchange,提问作者GenericName
相关产品推荐
相关产品推荐

