尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

用YOLO和图像分类识别宠物进食:从目标检测到本地推理服务部署

用YOLO和图像分类识别宠物进食:从目标检测到本地推理服务部署 小狗嘴里吃的竟然是……这句标题放在宠物视频里是“美食揭秘”放在技术场景里就是一个典型的实时目标检测 细粒度图像分类问题。这次我们要聊的不是萌宠吃播而是怎么用 YOLO 类目标检测模型、图像分类模型和视频流接入搭建一套自动识别“宠物嘴里在吃什么”的本地推理服务。先给结论这套思路可以落到 CPU 或低显存 GPU 上跑支持单张图片识别、视频抽帧识别和批量目录识别也可以暴露成 HTTP API 给监控摄像头或喂食器调用。下面按“能力速览 - 环境准备 - 部署启动 - 功能测试 - API 与批量任务 - 性能观察 - 排查 - 最佳实践”的顺序展开。从技术角度看“小狗嘴里吃的竟然是……”这条内容的核心是判断宠物进食行为和食物类别。传统做法是人眼回放监控视频成本高且容易漏。用视觉模型可以自动完成第一步检测“狗嘴”和“食物”的位置第二步裁剪出目标区域做细粒度分类第三步按时间窗口统计进食行为。这篇文章会演示一套可复用的识别链路并给出一份能直接跑通的本地部署方案读者可以用它来改造自家的宠物监控、智能喂食器或者做宠物内容素材的自动标签。1. 核心能力速览能力项说明项目类型宠物进食行为识别 / 目标检测 图像分类核心功能检测画面中的宠物嘴部与食物区域识别食物类别统计进食行为基础模型YOLO 系目标检测模型 ResNet 系图像分类模型可按实际需求选型硬件要求支持 CPU 推理GPU 可加速显存占用视模型规格而定通常 4G 显存即可跑轻量模型支持平台Windows / Linux / macOS依赖 PyTorch 环境启动方式Python 命令行启动 / HTTP API 服务是否支持 API支持可暴露 REST 接口供外部调用是否支持批量任务支持目录批量识别和视频抽帧识别输出格式JSON 结果 / 标注图片 / CSV 统计表适合场景宠物监控、智能喂食器、宠物内容自动标签、行为研究这里要说明模型参数、显存占用和识别精度需要以实际选型为准不同规模模型差距很大。下面给出的是通用部署与验证流程不是某个特定仓库的固定文档。2. 适用场景与使用边界这套识别链路适合谁先说适合的场景。第一类是宠物监控与智能喂食产品。在摄像头画面里实时判断“狗是否在进食”“吃的什么”可以联动喂食器调节出粮量也可以把异常进食行为推送给主人。第二类是宠物内容自动标签。做宠物短视频或图文素材库的同学可以用它自动生成“狗在吃”“吃的是狗粮/零食/水果”这类结构化标签省去人工看片标帧。第三类是宠物行为研究。研究者需要统计进食频次、单次进食时长、食物偏好用这套工具可以把视频抽帧后的标注结果导出成表格再做统计。不适合的场景也要说清楚实时性要求极高的交互场景比如毫秒级反馈的机械控制不建议用一整套检测分类串联延迟会偏高。拍摄角度极差、画面严重遮挡、逆光严重的场景识别率会明显下降。对“吃的东西是什么品牌”这种极细粒度识别通用分类模型做不到需要采集大量品牌样本做微调。合规边界同样是重点。涉及宠物和人脸的画面如果有主人或他人入镜不能随意采集、存储和公开。涉及宠物品牌食品的识别结果不能编造“某品牌赞助”或“某品牌测评结论”。做内容发布时素材需要有宠物主人的明确授权商标信息要按平台规范处理。不管技术多顺数据来源不合法应用就跑不长。3. 环境准备与前置条件先列一份基础环境清单按这个检查不容易出错。检查项说明操作系统Windows 10/11、Ubuntu 20.04、macOS 12 均可Python建议 3.9 到 3.11注意部分深度学习库对 3.12 兼容性一般包管理pip 或 condaGPU 驱动使用 NVIDIA GPU 时需安装对应版本驱动和 CUDA基础框架PyTorch 或 PaddlePaddle按模型仓库要求安装依赖库opencv-python、ultralytics、torchvision、fastapi、uvicorn、pandas磁盘空间模型文件约 100MB 到 2GB 不等数据集和输出结果另算端口API 服务默认 8000注意不要与本地其他服务冲突安装命令这里给两份参考一份是纯 CPU 版本一份是 GPU 版本。具体版本号请按本机环境替换。# CPU 版本 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu pip install ultralytics opencv-python pandas fastapi uvicorn requests # GPU 版本需要先确认本机 CUDA 版本 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics opencv-python pandas fastapi uvicorn requests安装完成后可以用下面这段代码快速验证环境是否可用import torch import cv2 from ultralytics import YOLO print(PyTorch:, torch.__version__) print(CUDA available:, torch.cuda.is_available()) print(OpenCV:, cv2.__version__)如果CUDA available输出False说明 PyTorch 安装的是 CPU 版本或者 GPU 驱动没有正确识别。后面的推理仍然可以跑只是速度会慢一些。检测模型的选择也简单说一下。YOLO 系列可以做通用目标检测先检测画面里的“狗”“嘴部”“食物”等目标。如果你只需要一个模型完成所有检测可以直接用 YOLO 的预训练权重跑通用场景要做精细的宠物嘴部检测最好自己标注一批数据集做微调。图像分类模型用 ResNet18、ResNet50 或者更轻量的 MobileNet 都可以目标是判断裁剪出来的食物区域属于哪个类别。4. 安装部署与启动方式完整流程分三步准备模型文件、编写推理脚本、启动服务。先建一个项目目录把模型权重、输入图片、输出目录分开管理。pet_eat_detector/ ├── models/ # 模型权重文件 │ ├── yolo.pt │ └── food_classifier.pth ├── inputs/ # 待识别图片或视频 ├── outputs/ # 识别结果图片和 JSON ├── scripts/ │ └── detect.py # 推理脚本 └── api_server.py # API 服务这里的目录结构是通用约定实际路径需要根据你的模型文件名和项目位置调整。4.1 编写目标检测和分类脚本下面给出一份完整的推理脚本先检测宠物嘴部与食物区域再对食物区域做分类。这份脚本可以直接复制到scripts/detect.py运行前把模型路径和图片路径替换成自己的。import argparse import json import time from pathlib import Path import cv2 import torch from ultralytics import YOLO from torchvision import models, transforms def load_classifier(model_path, num_classes, device): model models.resnet18(pretrainedFalse) model.fc torch.nn.Linear(model.fc.in_features, num_classes) state_dict torch.load(model_path, map_locationdevice) model.load_state_dict(state_dict) model.to(device) model.eval() return model def classify_food(classifier, image, device, class_names): transform transforms.Compose([ transforms.ToPILImage(), transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) tensor transform(image).unsqueeze(0).to(device) with torch.no_grad(): output classifier(tensor) pred_id torch.argmax(output, dim1).item() confidence torch.softmax(output, dim1)[0][pred_id].item() return class_names[pred_id], confidence def detect_image(detector, classifier, image_path, device, class_names): frame cv2.imread(str(image_path)) if frame is None: raise ValueError(f无法读取图片: {image_path}) results detector(frame, verboseFalse)[0] detections [] for box in results.boxes: x1, y1, x2, y2 box.xyxy[0].tolist() conf float(box.conf[0]) cls_id int(box.cls[0]) label results.names[cls_id] if label in [dog, cat, food, bowl]: crop frame[int(y1):int(y2), int(x1):int(x2)] food_label food_conf 0.0 if crop.size 0 and label in [food, bowl]: food_label, food_conf classify_food( classifier, crop, device, class_names ) detections.append({ bbox: [round(x1, 2), round(y1, 2), round(x2, 2), round(y2, 2)], detect_label: label, detect_conf: round(conf, 4), food_label: food_label, food_conf: round(food_conf, 4) }) return { image: str(image_path), detections: detections, elapsed_ms: round((time.time() - time_start) * 1000, 2) } if __name__ __main__: parser argparse.ArgumentParser() parser.add_argument(--image, typestr, requiredTrue) parser.add_argument(--detector, typestr, defaultmodels/yolo.pt) parser.add_argument(--classifier, typestr, defaultmodels/food_classifier.pth) parser.add_argument(--num-classes, typeint, default6) parser.add_argument(--classes, typestr, defaultdog_food,snack,fruit,vegetable,meat,other) parser.add_argument(--device, typestr, defaultcpu) args parser.parse_args() time_start time.time() device torch.device(args.device) detector YOLO(args.detector) class_names args.classes.split(,) classifier load_classifier(args.classifier, args.num_classes, device) result detect_image(detector, classifier, args.image, device, class_names) print(json.dumps(result, ensure_asciiFalse, indent2))运行方式cd pet_eat_detector python scripts/detect.py --image inputs/test.jpg --device cpu正常输出会是一个 JSON包含检测框坐标、检测置信度、食物分类结果和耗时。如果分类结果与图片实际内容一致说明链路已经跑通。4.2 启动 HTTP API 服务单张图片识别适合本地验证要做集成还是得走 API。下面用 FastAPI 写一个最小可用的识别接口支持上传图片文件并返回 JSON 结果。import io import json import time import cv2 import torch import uvicorn from fastapi import FastAPI, UploadFile, File from ultralytics import YOLO from torchvision import models, transforms app FastAPI() device torch.device(cuda if torch.cuda.is_available() else cpu) detector YOLO(models/yolo.pt) class_names [dog_food, snack, fruit, vegetable, meat, other] num_classes len(class_names) classifier models.resnet18(pretrainedFalse) classifier.fc torch.nn.Linear(classifier.fc.in_features, num_classes) classifier.load_state_dict(torch.load(models/food_classifier.pth, map_locationdevice)) classifier.to(device) classifier.eval() app.post(/predict) async def predict(file: UploadFile File(...)): image_bytes await file.read() image cv2.imdecode( np.frombuffer(image_bytes, dtypenp.uint8), cv2.IMREAD_COLOR ) start time.time() results detector(image, verboseFalse)[0] detections [] for box in results.boxes: x1, y1, x2, y2 box.xyxy[0].tolist() label results.names[int(box.cls[0])] if label in [dog, cat, food, bowl]: crop image[int(y1):int(y2), int(x1):int(x2)] food_label food_conf 0.0 if crop.size 0 and label in [food, bowl]: transform transforms.Compose([ transforms.ToPILImage(), transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) tensor transform(crop).unsqueeze(0).to(device) with torch.no_grad(): output classifier(tensor) pred_id torch.argmax(output, dim1).item() food_conf torch.softmax(output, dim1)[0][pred_id].item() food_label class_names[pred_id] detections.append({ bbox: [round(x1, 2), round(y1, 2), round(x2, 2), round(y2, 2)], detect_label: label, food_label: food_label, food_conf: round(food_conf, 4) }) return { detections: detections, elapsed_ms: round((time.time() - start) * 1000, 2) } if __name__ __main__: import numpy as np uvicorn.run(app, host127.0.0.1, port8000)启动命令cd pet_eat_detector python api_server.py启动后访问http://127.0.0.1:8000/docs可以看到 Swagger 调试页面也可以在页面里直接上传图片测试。这个服务只监听本地地址如果需要在局域网内调用把host改成0.0.0.0同时要用防火墙限制访问范围不要直接暴露到公网。5. 功能测试与效果验证部署完成后按下面的测试顺序逐项验证能更快定位问题。5.1 单张图片识别测试测试目的是确认“检测 分类”链路完整可用。操作步骤准备一张宠物进食图片画面里包含狗嘴和食物光线正常没有严重遮挡。放入inputs目录。执行推理脚本。观察输出 JSON 是否包含detections检测框是否贴合目标区域。预期结果输出 JSON 里至少有 1 个detections。detect_label包含dog、food或bowl中的至少一种。food_label与图片内容大致相符。判断标准检测框坐标位置合理没有跨整个画面乱框。food_conf不低于 0.5阈值可自行调整。常见失败原因问题可能原因检测框为空预训练 YOLO 不含宠物或食物类别需要微调分类结果错误食物类别太接近或分类模型训练样本不足脚本报错模型路径不对、类别数量与模型输出不一致、图片读取失败5.2 视频抽帧识别测试视频不能直接送进模型跑整段一般是抽帧后逐帧推理。下面给出一段视频抽帧识别脚本。import cv2 import json import time from pathlib import Path import torch from ultralytics import YOLO def extract_frames(video_path, output_dir, frame_interval10): cap cv2.VideoCapture(str(video_path)) if not cap.isOpened(): raise ValueError(f无法打开视频: {video_path}) output_dir Path(output_dir) output_dir.mkdir(parentsTrue, exist_okTrue) frame_count 0 saved_count 0 while True: ret, frame cap.read() if not ret: break if frame_count % frame_interval 0: out_path output_dir / fframe_{frame_count:06d}.jpg cv2.imwrite(str(out_path), frame) saved_count 1 frame_count 1 cap.release() return saved_count if __name__ __main__: detector YOLO(models/yolo.pt) device torch.device(cuda if torch.cuda.is_available() else cpu) video_path inputs/test.mp4 frame_dir outputs/frames results [] saved extract_frames(video_path, frame_dir, frame_interval10) print(f抽帧完成共保存 {saved} 帧) for frame_path in sorted(Path(frame_dir).glob(*.jpg)): frame cv2.imread(str(frame_path)) det detector(frame, verboseFalse)[0] for box in det.boxes: label det.names[int(box.cls[0])] if label in [dog, food, bowl]: x1, y1, x2, y2 box.xyxy[0].tolist() results.append({ frame: frame_path.name, bbox: [round(x1, 2), round(y1, 2), round(x2, 2), round(y2, 2)], label: label, conf: round(float(box.conf[0]), 4) }) with open(outputs/video_result.json, w, encodingutf-8) as f: json.dump(results, f, ensure_asciiFalse, indent2) print(f识别完成共 {len(results)} 个检测结果)测试要点视频分辨率过高时先做缩放比如缩到 1280 宽度能明显提升推理速度。抽帧间隔要按实际视频长度和活动速度调整狗进食动作变化快就加密抽帧。多段视频测试时要保持检测阈值一致方便横向比较。5.3 自定义参数测试模型推理一般有置信度阈值、IoU 阈值、图片尺寸这几个参数。YOLO 的conf和iou参数可以直接传detector YOLO(models/yolo.pt) results detector(frame, conf0.4, iou0.5, imgsz640, verboseFalse)[0]置信度阈值越高误检越少但漏检越多阈值越低检测框越多但要过滤的噪声也越多。实际使用时建议先在测试集上跑一遍统计不同阈值下的精确率和召回率再确定业务阈值。6. 接口 API 与批量任务HTTP API 已经在上文启动过了。这里补充调用示例、批量任务设计和常见失败处理。6.1 Python 调用 APIimport requests url http://127.0.0.1:8000/predict image_path inputs/test.jpg with open(image_path, rb) as f: response requests.post( url, files{file: f}, timeout30 ) print(response.status_code) print(response.json())如果返回 200detections数组里就是识别结果。如果返回 422说明上传字段名不对FastAPI 里字段名是file不能改成别的。6.2 curl 调用 APIcurl -X POST http://127.0.0.1:8000/predict \ -H Content-Type: multipart/form-data \ -F fileinputs/test.jpg6.3 批量任务设计批量场景通常不是一张张手动调用而是把一批图片或视频路径丢到一个处理队列里。简单做法是写一个脚本遍历目录逐张调用模型import json from pathlib import Path import torch from ultralytics import YOLO detector YOLO(models/yolo.pt) device torch.device(cuda if torch.cuda.is_available() else cpu) output_results [] input_dir Path(inputs/images) output_dir Path(outputs/batch) output_dir.mkdir(parentsTrue, exist_okTrue) for image_path in sorted(input_dir.glob(*.jpg)): result detector(str(image_path), conf0.4, devicedevice, verboseFalse)[0] item { image: str(image_path), detections: [] } for box in result.boxes: label result.names[int(box.cls[0])] if label in [dog, food, bowl]: x1, y1, x2, y2 box.xyxy[0].tolist() item[detections].append({ bbox: [round(x1, 2), round(y1, 2), round(x2, 2), round(y2, 2)], label: label, conf: round(float(box.conf[0]), 4) }) output_results.append(item) with open(output_dir / batch_result.json, w, encodingutf-8) as f: json.dump(output_results, f, ensure_asciiFalse, indent2)更稳妥的批量方案要加三样东西失败重试、断点续跑、进度日志。这里给出建议处理每条数据前先记录状态处理完标记为 success失败标记为 error。重试次数设为 3 次避免网络抖动或瞬时显存不足导致任务中断。输出文件按输入文件名命名方便对账。批量任务耗时较长时优先用 GPUCPU 模式只适合小规模验证。6.4 API 服务稳定性建议请求量上来以后一个请求一个模型推理会排队建议加消息队列或请求队列。模型权重加载完成后常驻内存不要在函数内部反复 load。如果同一时间内请求过多显存可能撑爆需要做并发数限制。对上传图片做格式和大小校验超限图片先压缩再推理。7. 资源占用与性能观察7.1 显存占用怎么观察运行推理时可以打开另一个终端用nvidia-smi实时观察nvidia-smi -l 1重点看两个指标Memory-Usage显存占用单位 MiB。GPU-UtilGPU 利用率单位百分比。如果只有模型加载而利用率很低说明瓶颈可能在 CPU 图像预处理或数据读取。7.2 CPU 推理和 GPU 推理的差异CPU 推理的优势是部署简单不需要显卡驱动适合小图和低频请求缺点是单张图片推理耗时明显偏高。GPU 推理在 batch 场景下优势很大几张图一起推理时吞吐量远高于 CPU。从实践经验看同样的 YOLO 模型CPU 单张推理耗时可能是 GPU 的 5 到 10 倍。具体数字受 CPU 型号、GPU 型号、图片分辨率、模型版本影响建议在自己机器上跑一组基准测试不要直接拿别人数据当结论。7.3 性能影响因素因素影响方向输入图片分辨率分辨率越高预处理和 DNN 计算耗时越大模型输入尺寸 imgszYOLO 默认 640改成 320 能提速但精度可能下降置信度阈值只影响后处理过滤不影响模型计算耗时批量大小批量增大能提高 GPU 利用率但显存占用同步上升视频抽帧间隔抽帧越密耗时越长行为连续性反而更好7.4 降低显存占用的方法选择轻量模型比如 YOLOv8n 而不是 YOLOv8x。推理时设置halfTrue用 FP16 精度显存占用可以降低约一半。限制并发请求数避免多批推理同时执行。用torch.cuda.empty_cache()在每批任务结束后释放显存碎片。detector YOLO(models/yolo.pt) results detector(frame, imgsz640, halfTrue, devicecuda)8. 常见问题与排查方法问题现象可能原因排查方式解决方案启动时提示无法导入 torchPython 版本不兼容或安装失败执行 pip list 查看 torch 是否安装按官方文档重新安装对应版本模型加载失败模型文件缺失或路径错误检查 models 目录下的文件是否存在确认模型路径或重新下载权重CUDA 不可用驱动问题或 PyTorch 安装成了 CPU 版运行 torch.cuda.is_available() 检查更新驱动重装对应 CUDA 版本 PyTorch检测结果为空预训练模型不包含目标类别打印检测结果确认类别名用自定义数据集微调检测模型显存不足批量太大或模型过大观察 nvidia-smi 的显存占用调小 batch、使用轻量模型、启用 FP16API 返回 422上传字段名不匹配检查请求参数名请求字段名改为 fileAPI 调用超时图片过大或 CPU 推理过慢查看服务端日志耗时压缩图片、换 GPU、增加超时时间批量任务中断内存不足或网络异常查看日志和输出目录增加重试机制逐张保存结果多路请求排队严重没有并发控制统计请求时延加请求队列限制并发数输出结果有大量重复框NMS 阈值过低或模型过拟合检查 IoU 阈值提高 iou 阈值或降低置信度阈值另一个容易被忽略的问题是环境变量污染。本机装了多个 Python 环境不同项目依赖版本冲突会让模型推理结果异常。建议每个项目用独立的 conda 环境或 venv。conda create -n pet_eat python3.10 conda activate pet_eat pip install -r requirements.txt端口冲突也常见。FastAPI 默认 8000如果提示端口被占用换一个端口启动即可python api_server.py --port 8001或者直接用 uvicorn 指定uvicorn api_server:app --host 127.0.0.1 --port 80019. 最佳实践与使用建议9.1 先小参数跑通再上规模第一次测试不要直接用超大视频和高分辨率图。先用一张 640x640 左右的小图跑通链路确认模型能加载、输出格式正确再逐步增加图片尺寸、视频长度和批量数量。这样能把“代码问题”和“性能问题”分开排查。9.2 保存最小可运行配置把写好的推理脚本、模型路径、阈值参数、类别名列表固定成一份独立配置文件避免换机器后参数漂移。{ detector: models/yolo.pt, classifier: models/food_classifier.pth, num_classes: 6, class_names: [dog_food, snack, fruit, vegetable, meat, other], conf_threshold: 0.4, iou_threshold: 0.5, imgsz: 640, device: cuda }python 脚本读这个配置文件启动以后调参数只改 JSON不改代码。9.3 数据与结果分目录管理建议按下面的结构组织pet_eat_detector/ ├── models/ # 只放模型权重不放进输入输出 ├── inputs/ │ ├── images/ # 单张图片测试 │ └── videos/ # 视频测试 ├── outputs/ │ ├── frames/ # 视频抽帧 │ ├── batch/ # 批量结果 │ └── logs/ # 运行日志 ├── config.json ├── requirements.txt └── api_server.py模型、输入、输出、日志分开放批量任务中断后也方便定位。9.4 批量任务加日志和重试批量处理最容易踩坑的就是“跑一半挂了前面结果全丢”。解决思路是每条样本处理完立刻落盘同时写日志。下面给出一个简单的进度日志写法import logging logging.basicConfig( levellogging.INFO, format%(asctime)s [%(levelname)s] %(message)s, handlers[ logging.FileHandler(outputs/logs/batch.log, encodingutf-8), logging.StreamHandler() ] ) for idx, image_path in enumerate(image_paths, 1): try: # 推理逻辑 logging.info(f[{idx}/{len(image_paths)}] 处理完成: {image_path.name}) except Exception as e: logging.error(f[{idx}/{len(image_paths)}] 处理失败: {image_path.name}, 错误: {e})9.5 接口服务注意访问控制API 服务不要直接绑定公网。如果必须在局域网内使用绑定0.0.0.0后要配合防火墙规则只放行指定 IP。也可以加一层 token 校验from fastapi import Header, HTTPException def verify_token(x_api_key: str Header(...)): if x_api_key ! your_token: raise HTTPException(status_code401, detailInvalid token)9.6 数据合规与授权提醒这里必须反复强调宠物画面中如果出现人物、其他宠物、可识别的门牌号或家庭环境都可能涉及隐私问题。采集监控视频前需要获得宠物主人同意发布识别结果或截图时要对人物人脸做模糊处理涉及宠物食品品牌时不要擅自使用品牌标志做封面或商业宣传。训练分类模型时如果使用了网上的图片也要注意图片版权和肖像权。技术上顺手不代表可以绕过授权。10. 总结与下一步先明确一个判断用 YOLO 检测目标区域再用分类模型识别食物类别这个思路做“宠物嘴里在吃什么”是可行的结构也简单适合作为宠物监控、智能喂食器或宠物内容标签工具的基础链路。最值得先验证的功能是单张图片的“检测 分类”全流程重点看两个部分能不能衔接上目标检测框是否稳定贴合宠物嘴部和食物区域分类模型裁剪后的识别结果是否一致。最容易踩的坑不是模型精度而是类别数设置和标签映射问题num_classes和class_names写错会导致模型直接加载失败。如果现在想继续扩展可以从这几个方向入手用真实宠物场景数据微调 YOLO增加“狗嘴”和“进食状态”类别。把分类模型从 ResNet 换成 MobileNet 或轻量 ViT在边缘设备上跑。增加行为统计模块按时间窗计算进食时长、进食次数和食物偏好。接入 RTSP 监控流做成实时推送到手机或微信的告警服务。这套方案不复杂但链路长涉及检测、分类、视频处理、接口服务和批量任务。建议先按上面的流程跑通最小示例再根据实际数据逐步优化。越早把输入输出格式和目录结构定下来后面接真实业务时就越省事。
返回列表