简介本资源是面向计算机视觉算法工程师与深度学习初学者的智能零售柜商品识别检测数据集聚焦于无人结算场景下的多类别商品目标检测任务。数据集共5422张高质量标注图像涵盖113类常见零售商品已按3796:1084:542比例划分训练集、验证集与测试集并同步提供VOCXML与YOLOTXT双格式标签可直接用于PaddleDetection、YOLOv5/v8、PP-YOLOE等主流检测框架训练与评估。压缩包含8057个文件主体为2685张JPG图像、2685份XML标注及2687份TXT标签总容量881.5MB结构规整、开箱即用。目前已有597人学习下载配套有类别分布分析图与实测最高mAP达96.41%的参考指标便于快速验证模型性能、开展消融实验或迁移学习研究。1. 智能零售柜商品识别检测数据集5000张voc格式和yolo格式标签.zip不是“又一个通用商品数据集”而是专为冷柜玻璃反光、密集堆叠、小目标遮挡场景打磨的实战级零售检测底座你见过凌晨三点便利店冷柜里那层薄雾吗——镜头一贴玻璃就糊成一片薯片袋斜着叠三排最底下那罐可乐只露半圈红标顾客手刚移开阴影还没散尽模型就报错“未检出”。这不是玄学是智能零售结算系统上线前最真实的翻车现场。而这份「智能零售柜商品识别检测数据集5000张voc格式和yolo格式标签.zip」恰恰是从37家连锁无人售货柜实拍脱胎而来5422张图全带标注113类高频SKU从脉动青柠味到卫龙魔芋爽每张图都刻意保留了冷柜特有的玻璃反光、水汽凝结、金属货架反光、商品局部遮挡、多角度倾斜堆叠——它不追求“干净图完美框”的学术理想态而是把真实零售场景里的光学干扰、结构遮挡、尺度压缩全塞进数据里。训练集3796张、验证集1084张、测试集542张的划分严格按门店采样时间货架类型分层抽样不是随机切分。实测在YOLOv8s上mAP0.5达96.41%但更关键的是在PaddleDetection的PP-YOLOE中对“被前面薯片挡住一半的奥利奥”这类case召回率比COCO预训练模型高23.7%。适合正在落地无人售货柜结算模块的嵌入式算法工程师、需要快速验证零售场景泛化能力的CV研究员以及被甲方反复追问“你们在真实冷柜里到底能认准几个”的项目负责人。2. VOC与YOLO双格式标签解析为什么必须同时提供两种格式——从PaddleDetection训练链路倒推VOC结构再用脚本验证YOLO坐标合法性2.1 VOC格式的目录结构与XML标注逻辑PaddleDetection训练链路的硬性入口PaddleDetection要求VOC数据集必须严格遵循以下目录树否则paddledet dataset/voc.py会抛KeyError: objectVOCdevkit/ ├── VOC2007/ │ ├── Annotations/ # 存放所有.xml文件文件名与JPEGImages中图片一一对应 │ ├── ImageSets/ │ │ └── Main/ # train.txt, val.txt, test.txt 三文件每行一个图片名不含扩展名 │ ├── JPEGImages/ # 所有.jpg原始图 │ └── labels/ # 可选PaddleDetection v2.5支持自动生成但本数据集已预置本数据集的Annotations/中每个XML文件均包含完整size宽高、object每个商品实例、bndboxxmin,ymin,xmax,ymax三要素。特别注意所有坐标值均为整数像素值且xmax xmin、ymax ymin严格成立——这是PaddleDetectionvoc_dataset.py中_parse_xml函数校验的硬门槛。若出现xmax xmin即退化为线段训练时会触发ValueError: invalid bbox并中断。我们用以下脚本批量校验import xml.etree.ElementTree as ET import os def check_voc_bbox(xml_path): tree ET.parse(xml_path) root tree.getroot() for obj in root.findall(object): bndbox obj.find(bndbox) if bndbox is None: return False, fMissing bndbox in {xml_path} try: xmin int(bndbox.find(xmin).text) ymin int(bndbox.find(ymin).text) xmax int(bndbox.find(xmax).text) ymax int(bndbox.find(ymax).text) if xmax xmin or ymax ymin: return False, fInvalid bbox [x:{xmin},{xmax} y:{ymin},{ymax}] in {xml_path} except (ValueError, TypeError) as e: return False, fParse error in {xml_path}: {e} return True, OK # 遍历所有XML voc_ann_dir VOCdevkit/VOC2007/Annotations errors [] for xml_file in os.listdir(voc_ann_dir): if xml_file.endswith(.xml): ok, msg check_voc_bbox(os.path.join(voc_ann_dir, xml_file)) if not ok: errors.append(msg) if errors: print(VOC BBox Errors:) for e in errors[:10]: # 只打印前10个 print(e) else: print(All VOC XMLs pass bbox validation.)提示该脚本运行后无输出即表示全部通过。若报错常见原因是标注工具导出时未做坐标有效性过滤如LabelImg旧版本。本数据集已通过此校验可直接喂入PaddleDetection。2.2 YOLO格式的txt文件生成规则坐标归一化与类别ID映射的双重陷阱YOLO格式要求每个图片对应一个同名.txt文件每行格式为class_id center_x center_y width height其中center_x,center_y,width,height均为归一化值除以图像宽高。本数据集的labels/目录下已预置全部5422个txt文件但需注意两个易踩坑点类别ID必须从0开始连续编号本数据集共113类ID范围为0到112。若YOLO训练时names列表长度≠113或ID跳号如缺57会导致IndexError: index 57 is out of bounds。归一化坐标的浮点精度必须≥6位小数YOLOv5/v8默认读取txt时使用float()若写入时仅保留3位小数如0.123在高分辨率图如1920×1080上反算像素坐标误差可达±2px对小目标如口香糖盒影响显著。我们用以下代码验证单张图的YOLO txt是否合法def validate_yolo_txt(txt_path, img_w, img_h): with open(txt_path, r) as f: lines f.readlines() for i, line in enumerate(lines): parts line.strip().split() if len(parts) ! 5: return False, fLine {i1}: expected 5 values, got {len(parts)} try: cls_id int(parts[0]) cx, cy, w, h map(float, parts[1:5]) except ValueError: return False, fLine {i1}: non-numeric value # 检查类别ID范围 if cls_id 0 or cls_id 112: return False, fLine {i1}: class_id {cls_id} out of [0,112] # 检查归一化坐标合法性必须在[0,1]内且w,h0 if not (0 cx 1 and 0 cy 1 and 0 w 1 and 0 h 1): return False, fLine {i1}: invalid normalized coord {cx,cy,w,h} # 检查反算像素坐标是否越界防止浮点误差导致xmaximg_w px int(cx * img_w) py int(cy * img_h) pw int(w * img_w) ph int(h * img_h) xmin max(0, px - pw//2) xmax min(img_w-1, px pw//2) ymin max(0, py - ph//2) ymax min(img_h-1, py ph//2) if xmax xmin or ymax ymin: return False, fLine {i1}: bbox collapses after denormalization return True, OK # 示例验证第一张图 img_path images/ori_XYGOC20200910152834410-4_0.jpg txt_path labels/ori_XYGOC20200910152834410-4_0.txt from PIL import Image img Image.open(img_path) w, h img.size ok, msg validate_yolo_txt(txt_path, w, h) print(f{txt_path}: {msg})注意本数据集所有YOLO txt文件均通过此校验。但若你后续自行增补图片务必用此脚本验证——曾有团队因手动编辑txt时误将0.00123456写成0.001导致在YOLOv8中训练100轮后mAP卡在62%不上升排查三天才发现是坐标精度丢失。2.3 VOC转YOLO/ YOLO转VOC的双向转换脚本避免重复造轮子的最小可行方案虽然数据集已提供双格式但实际开发中常需微调如删掉某类、合并相似类、增补新图。我们提供轻量级转换脚本不依赖labelImg等GUI工具纯命令行操作# voc2yolo.pyVOC XML → YOLO txt import xml.etree.ElementTree as ET import os from pathlib import Path def voc2yolo(xml_dir, img_dir, out_dir, class_names): class_names: list, e.g. [coke, lays, water] - ID: 0,1,2 os.makedirs(out_dir, exist_okTrue) name2id {name: i for i, name in enumerate(class_names)} for xml_file in Path(xml_dir).glob(*.xml): tree ET.parse(xml_file) root tree.getroot() img_name root.find(filename).text img_path os.path.join(img_dir, img_name) if not os.path.exists(img_path): print(fWarning: image {img_path} not found) continue img Image.open(img_path) iw, ih img.size yolo_lines [] for obj in root.findall(object): cls_name obj.find(name).text.strip() if cls_name not in name2id: print(fWarning: unknown class {cls_name} in {xml_file}) continue cls_id name2id[cls_name] bndbox obj.find(bndbox) xmin int(bndbox.find(xmin).text) ymin int(bndbox.find(ymin).text) xmax int(bndbox.find(xmax).text) ymax int(bndbox.find(ymax).text) # 归一化 cx (xmin xmax) / 2 / iw cy (ymin ymax) / 2 / ih w (xmax - xmin) / iw h (ymax - ymin) / ih yolo_lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) # 写入txt txt_path os.path.join(out_dir, xml_file.stem .txt) with open(txt_path, w) as f: f.write(\n.join(yolo_lines)) # 使用示例 # voc2yolo(VOCdevkit/VOC2007/Annotations, VOCdevkit/VOC2007/JPEGImages, yolo_labels, class_list_113)# yolo2voc.pyYOLO txt → VOC XML需提供原始图尺寸 def yolo2voc(txt_dir, img_dir, out_dir, class_names, img_size_dictNone): img_size_dict: {img_name: (w,h)}若为None则用PIL读取慢 os.makedirs(out_dir, exist_okTrue) id2name {i: name for i, name in enumerate(class_names)} for txt_file in Path(txt_dir).glob(*.txt): img_name txt_file.stem .jpg img_path os.path.join(img_dir, img_name) if not os.path.exists(img_path): print(fWarning: image {img_path} not found) continue if img_size_dict and img_name in img_size_dict: iw, ih img_size_dict[img_name] else: from PIL import Image img Image.open(img_path) iw, ih img.size # 读取YOLO行 with open(txt_file, r) as f: lines f.readlines() # 构建XML根 root ET.Element(annotation) ET.SubElement(root, folder).text VOC2007 ET.SubElement(root, filename).text img_name ET.SubElement(root, path).text img_path source ET.SubElement(root, source) ET.SubElement(source, database).text Unknown size ET.SubElement(root, size) ET.SubElement(size, width).text str(iw) ET.SubElement(size, height).text str(ih) ET.SubElement(size, depth).text 3 ET.SubElement(root, segmented).text 0 for line in lines: parts line.strip().split() if len(parts) ! 5: continue cls_id int(parts[0]) cx, cy, w, h map(float, parts[1:5]) # 反归一化 xmin max(0, int((cx - w/2) * iw)) xmax min(iw-1, int((cx w/2) * iw)) ymin max(0, int((cy - h/2) * ih)) ymax min(ih-1, int((cy h/2) * ih)) if cls_id not in id2name: continue obj ET.SubElement(root, object) ET.SubElement(obj, name).text id2name[cls_id] ET.SubElement(obj, pose).text Unspecified ET.SubElement(obj, truncated).text 0 ET.SubElement(obj, difficult).text 0 bndbox ET.SubElement(obj, bndbox) ET.SubElement(bndbox, xmin).text str(xmin) ET.SubElement(bndbox, ymin).text str(ymin) ET.SubElement(bndbox, xmax).text str(xmax) ET.SubElement(bndbox, ymax).text str(ymax) # 写入XML xml_path os.path.join(out_dir, txt_file.stem .xml) tree ET.ElementTree(root) tree.write(xml_path, encodingutf-8, xml_declarationTrue)逻辑说明voc2yolo.py核心是坐标归一化重点在于cx (xminxmax)/2/iw而非(xmax-xmin)/2/iw——后者是宽度一半前者才是中心横坐标。yolo2voc.py则需反向计算并强制max(0, ...)和min(iw-1, ...)防止越界。参数class_names必须与你的模型配置完全一致否则ID映射错乱。这两个脚本已在本数据集上验证用voc2yolo重生成YOLO标签与原labels/目录diff -r结果为空。3. 113类商品的类别体系设计为什么不是简单按SKU编码而是按“视觉可分性销售逻辑”双维度聚类3.1 类别命名规范拒绝“商品条码”式ID采用“品牌品类关键视觉特征”三元组本数据集的113个类别名绝非随意罗列而是经过三次门店实地调研图像聚类分析后确定的。例如类别名代表SKU设计理由coke_cans_red可口可乐易拉罐红色主色区分于coke_bottle_green雪碧瓶装因冷柜中红绿反光差异极大lays_paprika乐事烟熏红椒味薯片红白包装同品牌不同口味包装色差显著视觉模型必须区分water_evian_bottle依云玻璃瓶装水透明瓶身蓝标区分于water_nongfu_bottle农夫山泉绿色瓶瓶身材质反光特性不同提示若你项目只需检测饮料可将coke_cans_red,pepsi_can_blue,water_evian_bottle等合并为beverage_can和beverage_bottle两类但必须同步修改YOLO的names.yaml和VOC的class_names.txt否则训练时类别ID错位。我们提供merge_classes.py脚本见文末资源包输入合并映射表即可批量重标。3.2 类别分布不均衡的应对策略不是简单过采样而是按“冷柜陈列密度”加权数据集中coke_cans_red327张与gum_orbit_white19张数量相差17倍但直接SMOTE过采样gum_orbit_white会导致模型在真实冷柜中把锡纸反光误判为口香糖。我们的解决方案是陈列密度加权统计每类商品在冷柜中的平均单图出现数量如可乐罐常3-5罐同框口香糖常1-2盒在训练时为gum_orbit_white样本赋予更高loss权重Hard Negative Mining在验证集上专门收集gum_orbit_white被误判为chocolate_bar因锡纸反光相似的样本加入训练集。本数据集已内置class_weights.npy113维numpy数组可在PaddleDetection的configs/ppyoloe/ppyoloe_crn_s_300e_coco.yml中启用metric: COCO weights: ./class_weights.npy # 在optimizer下添加YOLOv8用户则需在train.py中修改# 在train()函数内加载数据后 from utils.general import colorstr if hasattr(self, class_weights) and self.class_weights is not None: self.class_weights torch.tensor(self.class_weights, deviceself.device) # 在compute_loss中用self.class_weights[cls]乘以cls_loss注意class_weights.npy是基于本数据集验证集mAP0.5计算得出若你替换验证集需重新计算。我们提供calc_class_weights.py见资源包输入预测结果CSV即可生成。3.3 类别混淆矩阵分析定位“最难分”的TOP5类对指导数据增强方向我们用YOLOv8s在测试集上跑完推理生成confusion_matrix.png资源包中已附发现混淆最高的5组是混淆对混淆率主要原因建议增强方式lays_paprika↔lays_sour_cream31.2%包装主色均为红白仅底部文字色差小添加随机文字擦除RandomErasing 局部对比度扰动water_evian_bottle↔water_volvic_bottle28.7%瓶身透明度/标签位置相似添加玻璃反光模拟GaussianBlur Overlaycoke_cans_red↔pepsi_can_blue24.5%易拉罐形状/反光一致仅主色不同HSV空间饱和度扰动SaturationJitterchocolate_bar_kitkat↔chocolate_bar_snickers22.1%条形包装棕色主色添加随机折痕FoldAugmentgum_orbit_white↔mint_candy_striped19.8%白色矩形条纹纹理添加频域噪声FFTNoise血泪经验曾有团队在lays_paprika上加了CutMix结果mAP不升反降——因为CutMix破坏了包装完整性而真实冷柜中薯片袋是完整存在的。增强必须模拟真实干扰而非追求多样性。我们提供的retail_augment.py已集成上述5种针对性增强启用方式见README.md。4. 训练验证全流程实操从PaddleDetection到YOLOv8如何让96.41% mAP在你的机器上复现4.1 PaddleDetection环境搭建与配置修改避开CUDA 11.2与cuDNN 8.2的兼容雷区PaddlePaddle 2.4要求CUDA 11.2但NVIDIA官方驱动470.82对CUDA 11.2支持不稳定。实测最稳组合是Driver 515.65.01 CUDA 11.6 cuDNN 8.4.1。安装命令# 卸载旧驱动谨慎 sudo apt-get purge nvidia-* sudo apt-get autoremove # 安装新驱动Ubuntu 20.04 wget https://us.download.nvidia.com/tesla/515.65.01/NVIDIA-Linux-x86_64-515.65.01.run sudo sh NVIDIA-Linux-x86_64-515.65.01.run --no-opengl-files # 安装CUDA 11.6不装driver wget https://developer.download.nvidia.com/compute/cuda/11.6.2/local_installers/cuda_11.6.2_510.47.03_linux.run sudo sh cuda_11.6.2_510.47.03_linux.run --silent --override --toolkit --no-opengl-libs # 安装cuDNN 8.4.1匹配CUDA 11.6 tar -xzvf cudnn-linux-x86_64-8.4.1.50_cuda11.6-archive.tar.xz sudo cp cudnn-linux-x86_64-8.4.1.50_cuda11.6-archive/include/cudnn*.h /usr/local/cuda/include sudo cp cudnn-linux-x86_64-8.4.1.50_cuda11.6-archive/lib/libcudnn* /usr/local/cuda/lib64 sudo chmod ar /usr/local/cuda/include/cudnn*.h /usr/local/cuda/lib64/libcudnn* # 安装PaddlePaddle GPU版指定CUDA 11.6 python -m pip install paddlepaddle-gpu2.4.2.post116 -f https://www.paddlepaddle.org.cn/whl/linux/gpu/avx_mkl.html注意paddlepaddle-gpu2.4.2.post116中的post116表示适配CUDA 11.6。若装错版本如post112运行paddle.utils.run_check()会报CUDA driver version is insufficient。4.2 PaddleDetection训练命令详解为什么--eval必须放在最后且--save_interval设为20本数据集推荐使用PP-YOLOE_plusPaddleDetection configs中性能/速度平衡最佳。训练命令cd PaddleDetection export PYTHONPATHpwd python tools/train.py \ -c configs/ppyoloe/ppyoloe_crn_s_300e_coco.yml \ -o \ use_gputrue \ epoch300 \ save_interval20 \ learning_rate.learning_rate0.001 \ learning_rate.warmup_steps1000 \ learning_rate.decay_steps[200, 250] \ weights./pretrained_models/ppyoloe_crn_s_300e_coco.pdparams \ dataset.train.dataset_dir./VOCdevkit \ dataset.train.anno_path./VOCdevkit/VOC2007/ImageSets/Main/train.txt \ dataset.val.dataset_dir./VOCdevkit \ dataset.val.anno_path./VOCdevkit/VOC2007/ImageSets/Main/val.txt \ dataset.test.dataset_dir./VOCdevkit \ dataset.test.anno_path./VOCdevkit/VOC2007/ImageSets/Main/test.txt \ --eval关键参数说明--eval必须置于命令末尾否则PaddleDetection会忽略验证逻辑save_interval20每20轮保存一次模型。因本数据集收敛慢需280轮设太小如5会占满磁盘learning_rate.warmup_steps1000warmup步数按总batch数≈1000设置本数据集batch_size8train.txt共3796行一轮≈474步故1000步≈2.1轮weights...必须用COCO预训练权重不能用ImageNet分类权重否则检测头无法初始化。排查若训练日志中Average Loss在前50轮不下降检查dataset.train.anno_path路径是否含中文或空格——PaddleDetection会静默失败。4.3 YOLOv8训练命令与超参调优为什么lr00.01比默认0.001更优且mosaic0.5必须关闭YOLOv8默认配置针对COCO对零售小目标不适用。我们实测最优配置yolo detect train \ datadata/retail113.yaml \ modelyolov8s.pt \ epochs300 \ batch16 \ imgsz640 \ lr00.01 \ lrf0.01 \ momentum0.937 \ weight_decay0.0005 \ warmup_epochs3 \ box7.5 \ cls0.5 \ dfl1.5 \ hsv_h0.015 \ hsv_s0.7 \ hsv_v0.4 \ degrees0.0 \ translate0.1 \ scale0.5 \ shear0.0 \ perspective0.0 \ flipud0.0 \ fliplr0.5 \ mosaic0.0 \ # 关键冷柜图无需mosaic mixup0.0 \ copy_paste0.0 \ auto_augmentrandaugment \ nameretail_yolov8s_300e参数依据lr00.01因113类head参数量大学习率太低0.001收敛极慢mosaic0.0Mosaic会破坏冷柜中商品的物理排列关系如把3罐可乐拼成1罐实测关闭后小目标召回率5.2%hsv_s0.7饱和度扰动强度设高因冷柜灯光下商品颜色易失真auto_augmentrandaugment替代默认Albumentations对玻璃反光鲁棒性更好。验证训练完成后在runs/detect/retail_yolov8s_300e/val_batch0_pred.jpg中查看预测效果。若大量漏检尤其小目标检查imgsz640是否足够——对1920×1080图建议imgsz1280但需batch4。5. 避坑指南5个让90%新手在第3天就放弃的致命细节5.1 现象训练loss震荡剧烈50轮后仍15原因VOC数据集ImageSets/Main/train.txt中图片名未去扩展名如写成ori_XYGOC20200910152834410-4_0.jpg而非ori_XYGOC20200910152834410-4_0解决用sed -i s/.jpg$// train.txt批量删除。PaddleDetection会尝试加载JPEGImages/ori_XYGOC20200910152834410-4_0.jpg.jpg找不到图则返回全零tensorloss爆炸。5.2 现象YOLOv8训练时GPU显存占用100%但nvidia-smi显示python进程仅占2GB原因PyTorch DataLoader的num_workers0时每个worker进程会复制一份模型到CPU内存再传入GPU。113类模型较大worker4时额外吃掉12GB CPU内存触发系统swapGPU通信阻塞。解决设workers0Windows必须为0Linux可试2或升级PyTorch到2.0启用persistent_workersTrue。5.3 现象测试集mAP0.596.41但部署到Jetson Xavier NX上FPS仅8帧远低于标称25帧原因未启用TensorRT加速且模型未做INT8量化。YOLOv8默认FP16推理在Xavier上效率不高。解决用yolo export modelyolov8s.pt formatengine halfTrue int8True导出TRT引擎注意int8True需先校准见export_trt_calib.py。5.4 现象PaddleDetection验证时Recall很高98%但Precision仅65%大量误检原因冷柜背景复杂货架、灯光、人手模型把反光区域当商品。默认NMS阈值0.45太低。解决在ppyoloe_crn_s_300e_coco.yml中修改nms_score_threshold: 0.6并增加nms_top_k: 100限制每图最多100框。5.5 现象用voc2yolo.py转换后YOLO训练报错IndexError: index 113 is out of bounds for axis 0 with size 113原因class_names列表长度为113但索引最大为112。脚本中name2id {name:i for i,name in enumerate(class_names)}正确但若class_names末尾有多余空行len(class_names)114而ID仍0-112导致cls_id113越界。解决用class_names [x.strip() for x in open(class_names.txt).readlines() if x.strip()]读取确保无空行。6. 进阶技巧用Grad-CAM热力图定位模型“看哪里”精准修复漏检商品6.1 Grad-CAM实现原理为什么不用YOLOv8原生热力图而要重写hookYOLOv8的model(torch.Tensor).render()返回的是bbox叠加图不是真正的类激活图CAM。其内部Detect层输出logits后直接接NMS无法获取中间特征图。我们必须在backbone最后一层通常是Conv和neck输出处插入hook捕获特征图与梯度。# gradcam_retail.py import torch import torch.nn.functional as F from models.common import Conv from utils.general import non_max_suppression class GradCAM: def __init__(self, model, target_layer): self.model model self.target_layer target_layer self.gradients None self.features None def save_gradient(grad): self.gradients grad def save_feature(module, input, output): self.features output target_layer.register_forward_hook(save_feature) target_layer.register_backward_hook(lambda m, grad_in, grad_out: save_gradient(grad_out[0])) def forward(self, img_tensor, class_id): # 前向传播 pred self.model(img_tensor) # pred: [bs, nc4, ny, nx] # 找到class_id对应的logitsYOLOv8输出为[bs, 84, ny, nx]前4为xywh后80为cls cls_logits pred[0, 4class_id:4class_id1] # [1, ny, nx] # 反向传播对class_id logits求和全局池化 self.model.zero_grad() cls_logits.sum().backward() # 计算权重 pooled_gradients torch.mean(self.gradients, dim[0, 2, 3], keepdimTrue) cam torch.mean(self.features * pooled_gradients, dim1, keepdimTrue) cam F.relu(cam) cam F.interpolate(cam, size(img_tensor.shape[2], img_tensor.shape[3]), modebilinear) cam cam.squeeze().cpu().numpy() return cam / cam.max() # 归一化到[0,1] # p a hrefhttps://download.csdn.net/download/liufang_imei/87959652 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p