
“你都看到了谁”这句话放在监控画面、活动照片或者一段视频素材里本质上是在问画面里有哪些人、分别在什么位置、总共有多少人、能不能继续做轨迹或行为分析。放到计算机视觉里对应一条很标准的工程链路——目标检测、人影识别、人数统计、批量处理和接口服务化。这篇文章不绑定某个具体开源仓库而是把这条链路从模型选择、环境配置、服务部署、功能测试到 API 封装完整走一遍。你拿到任何一个基于 OpenCV 或 YOLO 系模型的检测项目都可以按这个思路快速跑通。先说几个关键结论方便判断值不值得看硬件门槛不算高。单张图片检测用 CPU 就能跑视频流或摄像头实时处理建议有 NVIDIA 显卡。部署难度低于很多 AIGC 项目。核心依赖基本是 Python 环境加 OpenCV、NumPy、ONNX Runtime。可扩展性不错。检测结果既能输出到本地图片也能打包成 HTTP 接口还能挂到批量任务目录里跑。下面按工程落地顺序拆解。1. 核心能力速览下面这组指标按常见开源检测模型的标准用法整理不绑定某个具体仓库版本具体参数以你选择的模型权重为准。能力项说明项目类型计算机视觉目标检测 / 人影识别 / 人群分析典型技术栈OpenCV、ONNX Runtime、PyTorch可选、FastAPI主要功能图片与视频中的人体、人脸、常见物体检测输出坐标、类别、置信度推荐硬件CPU 可运行视频流与摄像头场景建议 NVIDIA GPU显存占用取决于模型规格、输入分辨率和批量大小需按实际测试确定支持平台Windows / Linux / macOS部分算子依赖需单独验证启动方式命令行脚本、HTTP 服务、Docker 容器可选API 能力可通过 FastAPI 封装图片上传、检测结果返回批量任务支持图片目录批量处理、视频文件列表排队推理适合场景图片检索、素材审核、人流统计、体育视频分析、AIGC 人影合规检查一句话总结这不是某个“全家桶”软件而是一条可组合的检测技术路线。模型负责“认人”OpenCV 负责“画框”FastAPI 负责“对外服务”三部分解耦方便按自己的场景替换其中任意一段。2. 适用场景与使用边界2.1 适合做什么图片素材库人形检索从大量照片里筛选包含人物的图片按坐标裁出人脸或人体区域。人流统计对商场、园区、展厅的单目摄像头画面做人数统计输出每个时间点的在场人数。体育视频分析识别运动员位置配合跟踪算法生成跑动轨迹、热点区域。AIGC 审核批量检查 AI 生成图片里是否出现未预期人物判断人物数量是否超出合规范围。2.2 不适合做什么身份核验。通用目标检测模型只能回答“这里有人”回答不了“这个人是谁”。人脸比对、证件照核验是另一条需要专门资质和合规渠道的技术路线。高精度人数统计。单目视角会出现重叠遮挡模型输出的是“检测框数量”不是“真实人头数”。想要精确计数需要设计跨帧去重或配合深度摄像头。低算力嵌入式设备直接跑大模型。如果板子只有 2GB 内存优先选择轻量模型并降低输入分辨率而不是直接加载标准尺寸的 ONNX 模型。2.3 隐私与合规边界这一点必须前置。凡是使用含真实人物图像的素材都要确认是否获得肖像授权在公共区域部署摄像头采集和分析需要符合当地隐私保护法规并在显眼位置公示用途。检测结果如果保存了带坐标的图片或 JSON 日志应限制访问权限不应该把包含人脸位置的原始数据放到公网目录里。3. 环境准备与前置条件3.1 硬件要求先看本机环境。CPU 可以使用 OpenCV DNN 或 ONNX Runtime 跑推理单张图片通常几百毫秒到几秒不等处理 1080p 视频流时CPU 很难达到实时帧率。NVIDIA GPU 能明显提速但先确认驱动能正常输出nvidia-smi能显示显卡型号和驱动版本说明 GPU 可用。显卡比较老也不要紧很多检测模型对显卡架构不敏感重点看显存容量和安装的 CUDA 版本是否匹配。显存大小直接决定输入分辨率和批大小8GB 以上会从容很多4GB 也可以运行轻量模型。3.2 软件依赖建议用 Python 3.8 或更高版本。核心依赖如下opencv-python负责图片读写、画框、NMS 后处理以及 DNN 推理。numpy数组操作和坐标计算。onnxruntimeONNX 模型推理相比 OpenCV DNN 在某些模型上兼容性更好。fastapi / uvicorn把检测逻辑封装成 HTTP 接口。python-multipartFastAPI 接收文件上传时使用。4. 安装部署与启动方式4.1 创建虚拟环境推荐先创建独立虚拟环境避免和系统 Python 包冲突。python -m venv venv source venv/bin/activate # Windows 下使用 venv\Scripts\activate4.2 安装依赖pip install -r requirements.txtrequirements.txt 内容示例opencv-python numpy onnxruntime fastapi uvicorn python-multipart requests4.3 准备模型文件常见检测模型导出后会得到两个文件模型文件通常是 .onnx 或 .pt例如yolo.onnx。类别名文件通常是.names例如coco.names每行一个类别名顺序必须和模型输出头一致。建议把模型文件和类别文件放进models/目录检测结果统一输出到outputs/目录。路径写死会让后续维护很痛苦尽量用相对路径加配置常量。4.4 单图检测脚本下面脚本是一个通用模板目标是把一张图片读进来、推理、画框、保存结果。这里以 YOLO 系常见的[1, N, 85]输出格式为例如果你的模型输出是[1, 84, N]或者其他格式需要按实际尺寸调整后处理。import cv2 import numpy as np MODEL_PATH ./models/yolo.onnx NAMES_PATH ./models/coco.names CONF_THRESHOLD 0.4 IOU_THRESHOLD 0.5 INPUT_SIZE (640, 640) def load_names(path): with open(path, r, encodingutf-8) as f: return [line.strip() for line in f.readlines()] def detect_image(image_path, output_path): names load_names(NAMES_PATH) net cv2.dnn.readNetFromONNX(MODEL_PATH) image cv2.imread(image_path) if image is None: raise ValueError(f读取图片失败: {image_path}) h, w image.shape[:2] blob cv2.dnn.blobFromImage( image, scalefactor1.0 / 255.0, sizeINPUT_SIZE, mean(0, 0, 0), swapRBTrue, cropFalse, ) net.setInput(blob) output net.forward() # 假设输出形状是 [1, N, 85]最后一维为 cx, cy, w, h 80 个类别分数 output output[0] boxes, confidences, class_ids [], [], [] scale_x w / INPUT_SIZE[0] scale_y h / INPUT_SIZE[1] for row in output: scores row[4:] class_id int(np.argmax(scores)) confidence float(scores[class_id]) if confidence CONF_THRESHOLD: continue cx, cy, bw, bh row[:4] * [scale_x, scale_y, scale_x, scale_y] x int(cx - bw / 2) y int(cy - bh / 2) boxes.append([x, y, int(bw), int(bh)]) confidences.append(confidence) class_ids.append(class_id) indexes cv2.dnn.NMSBoxes(boxes, confidences, CONF_THRESHOLD, IOU_THRESHOLD) if len(indexes) 0: indexes indexes.flatten() for i in indexes: x, y, bw, bh boxes[i] label f{names[class_ids[i]]} {confidences[i]:.2f} cv2.rectangle(image, (x, y), (x bw, y bh), (0, 255, 0), 2) cv2.putText(image, label, (x, y - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) cv2.imwrite(output_path, image) print(f检测完成共 {len(indexes) if len(indexes) 0 else 0} 个目标结果: {output_path}) if __name__ __main__: detect_image(inputs/test.jpg, outputs/result.jpg)跑一行命令验证基础链路python detect.py如果当前目录缺少inputs/test.jpg或模型文件先补齐再验证。5. 功能测试与效果验证项目能跑起来的第一个信号是输入一张图片输出一张带框的图片。不要直接上大分辨率视频先用单图确认模型、类别文件、后处理代码是否对齐。5.1 单张图片检测准备一张至少包含一个人物的图片放到inputs/目录。运行上面的脚本后检查outputs/result.jpg人物位置是否被正确框出。框是否明显偏移或过大过小。置信度数值是否可信如果所有目标都小于 0.25可能模型或输入尺寸有问题。5.2 视频文件检测单图稳定后再做视频检测。思路是逐帧读取、逐帧推理、合并写回视频import cv2 def detect_frame(frame, net, names): h, w frame.shape[:2] blob cv2.dnn.blobFromImage(frame, 1.0 / 255.0, (640, 640), swapRBTrue, cropFalse) net.setInput(blob) output net.forward()[0] boxes, confidences, class_ids [], [], [] scale_x w / 640.0 scale_y h / 640.0 for row in output: scores row[4:] class_id int(np.argmax(scores)) confidence float(scores[class_id]) if confidence 0.4: continue cx, cy, bw, bh row[:4] * [scale_x, scale_y, scale_x, scale_y] boxes.append([int(cx - bw / 2), int(cy - bh / 2), int(bw), int(bh)]) confidences.append(confidence) class_ids.append(class_id) indexes cv2.dnn.NMSBoxes(boxes, confidences, 0.4, 0.5) if len(indexes) 0: indexes indexes.flatten() for i in indexes: x, y, bw, bh boxes[i] cv2.rectangle(frame, (x, y), (x bw, y bh), (0, 255, 0), 2) return frame def detect_video(input_path, output_path): net cv2.dnn.readNetFromONNX(./models/yolo.onnx) cap cv2.VideoCapture(input_path) fps int(cap.get(cv2.CAP_PROP_FPS)) width int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) writer cv2.VideoWriter(output_path, cv2.VideoWriter_fourcc(*mp4v), fps, (width, height)) while True: ok, frame cap.read() if not ok: break frame detect_frame(frame, net, None) writer.write(frame) cap.release() writer.release() print(f视频检测完成: {output_path}) if __name__ __main__: detect_video(inputs/test.mp4, outputs/result.mp4)这里有个性能预期问题视频推理耗时等于“逐帧推理耗时 x 帧数”如果单帧要 80ms25fps 的视频每秒实际只能处理 12 帧左右生成的视频会明显比原视频慢。验证时先截取 10 秒片段测试不要一上来处理整个长视频。5.3 摄像头实时检测服务器或本机有摄像头时可以替换输入源cap cv2.VideoCapture(0) while True: ok, frame cap.read() if not ok: break frame detect_frame(frame, net, None) cv2.imshow(detect, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()摄像头场景最容易出现的问题是分辨率过高导致推理延迟明显建议先用cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)这类方式把输入分辨率限制住。5.4 判断标准与常见失败原因功能验证的通过标准可以划分为三层基础通过单张图片能输出带框结果类别名正确。稳定通过连续 100 张图片无崩溃、无内存持续上涨、漏检率可接受。生产通过视频或摄像头能稳定运行单帧耗时稳定显存不溢出。常见失败原因如下现象原因完全没有框模型路径错误、类别文件顺序错误、置信度阈值过高框位置偏移输入缩放比例计算错误、blob 的 scale 和 size 设置不一致同一目标多个框NMS 阈值设置不合理、后处理没有正确调用 NMSBoxes推理速度很慢输入分辨率过大、CPU 推理、模型文件过大视频写出后无法播放fourcc 编码不支持、fps 与原视频不一致6. 接口 API 与批量任务检测脚本只能自己用想要接到业务系统里需要包一层 HTTP 接口。下面用 FastAPI 封装一个最简单的图片检测接口。6.1 FastAPI 检测服务import os import uuid import cv2 from fastapi import FastAPI, UploadFile, File from fastapi.responses import FileResponse app FastAPI() TMP_INPUT_DIR ./tmp_input TMP_OUTPUT_DIR ./tmp_output os.makedirs(TMP_INPUT_DIR, exist_okTrue) os.makedirs(TMP_OUTPUT_DIR, exist_okTrue) app.post(/detect) async def detect(file: UploadFile File(...)): suffix os.path.splitext(file.filename)[-1] task_id uuid.uuid4().hex input_path os.path.join(TMP_INPUT_DIR, f{task_id}{suffix}) output_path os.path.join(TMP_OUTPUT_DIR, f{task_id}_result.jpg) with open(input_path, wb) as f: f.write(await file.read()) # 这里复用 detect_image 的逻辑可把模型加载放到 app 启动时完成 detect_image(input_path, output_path) return FileResponse(output_path, media_typeimage/jpeg) if __name__ __main__: import uvicorn uvicorn.run(app, host127.0.0.1, port8000)生产环境中模型初始化不应该放在每次请求里建议把net和names放到全局变量启动时加载一次请求时只走推理。启动服务uvicorn main:app --host 127.0.0.1 --port 80006.2 curl 调用示例curl -X POST http://127.0.0.1:8000/detect \ -F fileinputs/test.jpg \ -o outputs/api_result.jpgPython 端调用也一样import requests url http://127.0.0.1:8000/detect files {file: open(inputs/test.jpg, rb)} response requests.post(url, filesfiles, timeout30) if response.status_code 200: with open(outputs/api_result.jpg, wb) as f: f.write(response.content) print(接口调用成功) else: print(接口调用失败:, response.status_code)如果接口返回的不是图片而是 JSON更适合对接业务系统。可以在接口里把检测框、类别、置信度序列化后返回{ objects: [ { class: person, confidence: 0.83, box: [120, 45, 300, 520] } ] }具体返回结构取决于你自己的定义关键是先跑通“上传图片 - 获取结果”这个最小闭环。6.3 批量任务设计与失败重试批量任务的核心诉求是给一批文件程序自动逐个处理跑完输出结果清单。最简单的目录遍历方式import os from pathlib import Path input_dir Path(./inputs_batch) output_dir Path(./outputs_batch) output_dir.mkdir(exist_okTrue) for image_path in input_dir.glob(*.jpg): output_path output_dir / f{image_path.stem}_result.jpg try: detect_image(str(image_path), str(output_path)) except Exception as e: print(f处理失败: {image_path} - {e})更稳妥的批量任务要加进度记录和失败重试用一个results.csv记录每个文件的成功状态。失败文件单独写入failed.txt。重跑时跳过已经成功的结果只处理失败列表。每处理完一批打印这轮的耗时、平均单张耗时、失败数量。批量任务最常见的坑有两个一是内存持续上涨要确保每张图片处理后变量被回收二是中途崩溃导致前面白跑所以日志和断点续跑比单线程硬跑更值得投入时间。7. 资源占用与性能观察7.1 查看显存与 CPU 占用Linux 下实时观察 GPU 显存watch -n 2 nvidia-smiWindows 下可以使用任务管理器中的 GPU 性能面板。推理过程中主要观察显存占用是否稳定、是否持续增长。如果显存持续增长大概率是每帧生成的对象没有被回收或缓存没有清理。CPU 占用可以通过top或任务管理器查看。ONNX Runtime 默认会启用多线程小模型在 CPU 上可能只用到几十到几百 MB 内存视频场景则主要吃 CPU 算力。7.2 影响性能的主要因素模型规格输入分辨率越大计算量越大模型参数量越大消耗越高。帧率与分辨率1080p 视频需要逐帧缩放预处理时间不可忽略。批大小同一帧内同时检测多张图会显著增加显存和内存占用。后处理复杂度NMS、类别过滤、画框叠加在目标数量很多时也会影响整体延迟。7.3 降低资源占用的方法优先从输入分辨率下手。很多模型设计输入为 640x640但实际业务图更大先缩放能明显减少耗时。其次可以导出更小的模型变体例如将标准模型转成量化后的 ONNX INT8 模型。变换精度前需要先用一批真实数据做精度对比确认漏检率可以接受。如果显存不足还可以把推理从 GPU 切回 CPU 测试虽然变慢但至少不爆显存。视频场景启用帧抽帧策略例如每秒只检测 5 帧其余帧沿用上一帧结果也能大幅降低资源占用。8. 常见问题与排查方法问题现象可能原因排查方式解决方案依赖安装失败Python 版本不匹配查看 pip 报错信息升级 Python 或换用对应版本模型文件无法加载ONNX 算子版本不兼容检查 OpenCV/ONNX Runtime 版本重新导出模型或升级运行时CUDA 不可用驱动版本过旧或 PyTorch/ONNX 与 CUDA 版本不匹配运行nvidia-smi和推理日志更新显卡驱动或切换为 CPU 运行显存不足输入分辨率过大、批大小过大观察 nvidia-smi缩小输入尺寸、降批大小、换轻量模型页面或接口无法访问服务未启动或端口被占用检查日志与netstat -ano换端口或重启服务API 返回 413上传文件过大查看 Web 服务配置增大 body 限制或先压缩图片批量任务卡住单个文件阻塞或内存溢出查看日志定位卡住文件加超时控制和失败重试检测结果飘忽不定模型不适合场景、光照变化大用多个样张测试更换模型或补充后处理规则遇到问题时先做隔离把“加载模型 - 推理 - 后处理 - 写文件”四段分别打日志定位问题发生在哪一段。不要一次性改多个参数改完一项再验证。9. 最佳实践与使用建议第一次一定用小参数测试。不要一上来就处理 4K 长视频先跑一张图、一小段视频、一个只有两张图片的目录。保留一套最小可运行配置。模型文件、类别文件、测试图片、运行脚本整理进同一个项目目录记录使用的依赖版本避免换机器后环境对不上。输入素材、输出结果、日志文件分开管理。目录结构可以按inputs/ outputs/ logs/ models/组织清理数据时不会误删模型。检测接口要控制访问范围。FastAPI 默认监听 127.0.0.1对外提供服务时应放到内网或加授权不要直接把上传接口暴露到公网。批量任务必须加日志和失败重试。一句try except加一个失败列表能省下大量排查时间。涉及人脸、声音、版权素材时必须确认授权。目标检测本身只是框出物体但把结果用于人员追踪、统计或商业化分析时要遵守隐私和数据保护要求。发布或商用前要做效果复核。特定场景下漏检率可能很高例如背对镜头、遮挡严重、暗光环境。用至少几百张实际业务图做抽样验证比单独跑一张好看的效果图更有说服力。10. 总结与下一步“你都看到了谁”听起来像一个哲学问题在工程里它就是“目标检测模型输出的一组带类别和坐标的框”。这套链路最值得尝试的地方在于模块清晰、替换成本低模型可以换后处理可以改接口可以自行定义从零到能跑通一张图片可能只需要半天时间。最先应该验证的不是高级功能而是三个最小闭环单张图片能否正确输出带框结果连续图片处理是否稳定接口能否在局域网内被其他程序调用。最容易踩的坑集中在后处理和路径管理输出格式不对再强的模型也画不出正确位置路径写死换个目录就要改代码。后续可以继续扩展的方向也很明确加入跟踪算法做多目标轨迹分析增加人群密度估计把检测结果输出成结构化数据供报表使用或者在摄像头画面上做人脸脱敏处理后再存储。每一步都是在现有检测能力上叠加模块而不是另起炉灶。先把“识别”这件事跑稳后面的路就好走了。