首页 / 资讯中心 / 文章详情

Ubuntu+Python+YOLOv8n:免费客流统计系统搭建实践

Ubuntu+Python+YOLOv8n:免费客流统计系统搭建实践 ★ FEATURED ARTICLE
朋友开了家30平米左右的小店一直想知道每天到底有多少人进店、什么时段人最多。问了一圈客流统计设备单路摄像头配上云平台报价从几千到上万不等有些还要按月交服务费。对一家小店来说这个预算完全不现实。后来我干脆在他店里用一台闲置的Ubuntu主机搭了一套完全免费的人流统计方案摄像头取流、Python处理、开源轻量级目标检测模型做识别按虚拟线的规则去数进出人头。实测下来数据基本能用准确率能到九成以上成本几乎为零唯一付出的就是前期调试的时间。这篇文章把整套方案完整记录下来从选型、环境搭建、核心计数逻辑到准确率验证和踩坑经历一次性讲清楚给预算有限又想自己动手做门店、展会人流统计的朋友一个可以直接抄作业的参考。1. 先搞清楚一件事免费人流统计到底要解决什么问题1.1 客流统计不是简单地数个数很多第一次接触这个需求的人会以为客流统计就是在门口放个计数器有人经过就加一。其实真正有用的人流统计至少要分清楚方向进店多少人、出去多少人以及在什么时间段进出。这样才能算出进店率、高峰时段、平均停留时长这些经营指标。市面上的商用方案之所以贵是因为它把整条链路都做好了摄像头采集、边缘端或云端识别、数据报表、历史回查。你付的不只是硬件钱还有软件授权、云存储和后续维护。而这些恰恰是自建方案可以省掉的成本。但我建议做之前先明确一个边界不要一上来就追求和商用系统一样的功能完整度。第一版只需要做到准确区分进出方向、记录时间戳、统计每小时的人流量就够了。把这三个指标做扎实已经能满足大多数小店和中小型活动的人流统计需求。1.2 免费方案的账到底怎么算这里说的完全免费指的是软件和服务的费用为零Ubuntu系统本身免费、Python环境免费、所用的目标检测模型权重免费、跑模型不依赖任何云端付费接口。硬件则是利用手边现成的旧电脑和USB摄像头这部分一般不需要额外掏钱。不过有一个隐性成本必须坦白讲时间。你需要自己装环境、调参数、处理各种意外状况。相比之下商用方案买回来插电就能用这中间的差价其实就是你的调试时间成本。但只要过了前期调试这一关整套系统就变成了一次投入、长期免费跑的状态。我见过有人拿树莓派跑也有人拿一台七八年前的旧笔记本跑都没有问题。Linux系的系统在无人值守稳定性上确实比一般家用台式机要省心这也是我选择Ubuntu而不是Windows的重要原因。开机之后可以一个月不用管进程崩了还有定时任务可以自动拉起来。2. 技术选型背景差分、SSD、YOLO三条路线的实际权衡2.1 三条免费路线的优缺点对比做人流检测软件侧最核心的是识别出画面里的人。这一步有几条常见的免费路线我花了几天时间分别做了验证这里直接给结论。路线运算开销识别精度主要限制适用场景OpenCV背景差分MOG2很低一般对光照和影子极其敏感人静止就丢固定机位、环境光稳定的极简场景OpenCV DNNMobileNet-SSD低中等小目标容易漏检需要自己写追踪无GPU、CPU较弱的旧机器YOLO系列轻量模型n/s型号中低高模型权重有开源许可差异CPU可跑精度要求较高的场景背景差分以前用得很多但实际测试下来问题不少。店里下午阳光照进来门口的亮度一变画面中大量区域被判成前景人没进来计数却疯狂跳动。这种误报对最终统计结果打击很大我直接放弃了这条路线。MobileNet-SSD是另一条可用的路最大的优点是协议非常宽松Apache 2.0商用也无顾虑。缺点是它是在比较早的COCO数据集上训练的对行人的召回率比YOLO系差一些人小一点、角度偏一点就容易漏。市区门店门口那种两三米宽的通道人从侧面经过时经常只露出一半身体MobileNet-SSD的表现不够稳定。2.2 为什么最终落在YOLO系模型上综合测下来我把主力方案确定在YOLO系列具体用的是YOLOv8n这个最小号模型。原因有几个。第一YOLO系模型的检测能力经过了大规模数据集的训练对人这个类别的识别非常成熟尤其是对部分遮挡、不同姿态的鲁棒性明显好于MobileNet-SSD。测试中同样一个背对镜头的人YOLOv8n能稳定给出0.6以上的置信度而MobileNet-SSD经常掉到0.3以下然后被阈值过滤掉。第二ultralytics这个库把整个推理流程封装得很干净。不需要自己处理锚框、NMS这些底层的东西几行代码就能拿到检测框坐标和置信度。这对不做深度学习研究的开发者来说非常友好。第三关于性能YOLOv8n在纯CPU上处理640x640分辨率普遍能跑到5-10 FPS。我这个计数逻辑只要求跨线瞬间能捕捉到并不需要处理每一帧所以这个帧率完全够用。如果有NVIDIA显卡还能更快但没必要为了这个需求专门加显卡。2.3 开源许可的附加提醒免费和商用不是一回事这里必须说一个容易被忽略的坑。YOLOv8的模型权重和代码采用的是AGPL-3.0协议这个协议的特点是你可以免费使用如果你只是内部自己跑没有任何问题但如果你的系统要对外提供服务或者被集成到商业产品里交付给客户就需要谨慎评估开源义务。如果你做的是完全自用、不对外收费的项目比如自己店里统计客流用YOLOv8完全没问题。但如果你准备把这套方案包装成产品卖给商家我会建议改用YOLOv5s。YOLOv5是老版本检测精度比v8n略低一点但权重采用MIT协议商用限制少很多。还有一种思路是全面换成MobileNet-SSD精确度稍差但协议最宽松。我已经把许可因素纳入选型范围是因为确实有朋友照着我的方案做给客户落地结果法务审出问题又回头折腾了一遍。3. Ubuntu上的环境准备从第一行命令到第一帧画面3.1 最小硬件与系统前提先说硬件底线。我实际跑这套方案的是一台2013年左右的老台式机四核CPU8GB内存没有独立显卡通过一个几十块的USB摄像头取流。如果你手头只有双核CPU加4GB内存的机器也能跑但需要把输入分辨率进一步降低比如把摄像头分辨率调到480p再喂给模型。内存低于4GB的话不建议再跑图形界面直接用Ubuntu Server版更稳。系统方面推荐Ubuntu 22.04 LTS长期支持版本软件源里的依赖都比较新而且社区资料丰富遇到问题基本都能搜到答案。如果你用的是带桌面的Ubuntu记得在安装完系统后关闭自动锁屏和休眠免得跑着跑着屏幕一黑系统挂起视频流就断了。3.2 安装Python环境与系统依赖我的安装步骤基本是这样的直接在终端里依次执行。首先保证系统基础包是完整的sudo apt update sudo apt install -y python3-venv python3-pip接下来创建虚拟环境。这一步不能省因为后面要装的ultralytics会依赖特定版本的numpy等库直接装在系统Python里很容易污染环境哪天升级系统包就把依赖弄坏了。python3 -m venv ~/stream-env source ~/stream-env/bin/activate激活虚拟环境后安装核心依赖pip install opencv-python ultralytics安装OpenCV这里有一个高频坑必须提醒如果你用apt install python3-opencv装的是系统版OpenCV之后再pip install opencv-python两个版本会打架import的cv2到底是哪个全看运气。建议只保留pip安装的版本。另外很多Ubuntu Server环境是极简安装pip装完opencv-python后import cv2会报libGL.so.1缺失。解决方法是补装两个系统库sudo apt install -y libgl1 libglib2.0-0装完之后运行python3 -c import cv2; print(cv2.__version__)验证能输出版本号就说明图像库没问题了。3.3 接上摄像头USB与RTSP两种取流方式摄像头是这套系统的眼睛取流这块折腾了我不少时间。USB摄像头最简单插上后确认设备节点被识别ls /dev/video*看到/dev/video0之后就可以用OpenCV打开它。一开始测试时建议不要直接跑检测先写几行代码把画面显示出来确认角度和光线都没问题再做后面的步骤。import cv2 cap cv2.VideoCapture(0, cv2.CAP_V4L2) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) while True: ret, frame cap.read() if not ret: break cv2.imshow(view, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()如果是网络摄像头取流用RTSP地址大致是rtsp://用户名:密码摄像头IP:554/Streaming/Channels/101这种格式不同品牌略有差异。这里重点说两个坑一是RTSP流在OpenCV里默认缓冲很大延迟可能到好几秒可以用cap.set(cv2.CAP_PROP_BUFFERSIZE, 2)把缓冲压小二是网络不太稳定时cap.read()会长时间卡住需要加上超时重连的逻辑否则整个程序会假死。第一次运行YOLO模型时ultralytics会自动联网下载yolov8n.pt权重文件大小约6MB。如果服务器网络慢也可以直接到官方Release页面手动下载然后把权重文件放到代码所在目录它会自动读取本地文件不再重复下载。4. 核心计数逻辑虚拟线圈加人的中心点4.1 检测、追踪、跨线三层结构怎么配合把画面接入Python之后接下来的问题就是怎么从一段连续的视频里判断有一个人过去了。这需要拆成三个步骤检测、追踪、跨线判断。检测解决的是这一帧里哪些像素是一个人追踪解决的是上一帧那个人和这一帧这个人是不是同一个跨线判断则是在追踪的基础上盯住每一个人的中心点坐标看它在某个时刻是否跨越了我们提前画好的虚拟线。这三个步骤层层依赖最核心的要点是追踪。如果没有追踪每一帧都只做独立检测那么一个人从进画面到出画面会连续出现几十帧你没法知道他是一个人在走还是几十个人在走。加了追踪之后每个人身上挂一个ID只有这个ID跨越虚拟线的那一刻才计数一次。4.2 为什么我没用复杂的追踪器现在有很多现成的目标追踪算法比如SORT、DeepSORT、ByteTrackultralytics库里甚至自带了一行调用的追踪接口。但我在这套方案里没有用而是用了一段不到三十行的最近邻匹配代码。原因有两点。第一是为了让人完全理解计数逻辑第二是在这个小场景下复杂的追踪器并不会比最近邻匹配好多少。摄像头是固定安装的、帧率基本稳定、人的移动是连续的这些条件凑齐之后用上一帧每个人的中心点和这一帧检测到的所有中心点做距离匹配离得最近的就算同一个人。这个逻辑简单有效而且出了问题一眼就能看穿。当然它也有局限两个人并排走、互相遮挡时中心点匹配可能跳变。但对小店门口这种密度不高的场景来说这个牺牲是值得的换来的是代码完全可控出了问题不用去调试一个几百行的黑盒追踪器。如果你要处理的是地铁闸机、展会入口那种人流密集的场景再考虑换成现成的ByteTrack不迟。4.3 一套能直接跑通的主循环代码下面这版代码是我整理过的最小可用版本把检测、追踪、跨线判断和计数全部包含在内。先复制到本地跑起来再按自己场景调整参数。import cv2 from collections import defaultdict from datetime import datetime from ultralytics import YOLO # 加载模型首次运行会自动下载权重 model YOLO(yolov8n.pt) # 打开摄像头V4L2方式在Linux下更稳定 cap cv2.VideoCapture(0, cv2.CAP_V4L2) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) # 虚拟线位置高度为480的画面中y320 的位置 LINE_Y 320 # 置信度阈值低于这个值的不认为是人 CONF 0.45 # 追踪匹配的最大距离单位是像素 MATCH_DIST 80 # 追踪表track_id - (cx, cy) tracks {} next_id 1 in_count 0 out_count 0 while True: ret, frame cap.read() if not ret: break # 推理只保留类别0person results model(frame, confCONF, imgsz640, classes[0], verboseFalse)[0] # 从检测结果中提取所有人脸框的中心点 detections [] for box in results.boxes: x1, y1, x2, y2 map(int, box.xyxy[0]) cx, cy (x1 x2) // 2, (y1 y2) // 2 detections.append((cx, cy)) current_tracks {} used_det set() if tracks: for tid, (px, py) in tracks.items(): best_d None best_idx None for idx, (cx, cy) in enumerate(detections): if idx in used_det: continue d (cx - px) ** 2 (cy - py) ** 2 if best_d is None or d best_d: best_d d best_idx idx # 如果找到了距离足够近的新位置则沿用当前ID if best_idx is not None and best_d MATCH_DIST ** 2: used_det.add(best_idx) current_tracks[tid] detections[best_idx] # 跨线判断 prev_cy py cur_cy detections[best_idx][1] if prev_cy LINE_Y and cur_cy LINE_Y: out_count 1 print(f{datetime.now()} OUT id{tid}) elif prev_cy LINE_Y and cur_cy LINE_Y: in_count 1 print(f{datetime.now()} IN id{tid}) # 剩余未匹配的检测结果分配新ID for idx, (cx, cy) in enumerate(detections): if idx not in used_det: current_tracks[next_id] (cx, cy) next_id 1 tracks current_tracks print(fin{in_count} out{out_count} tracks{len(tracks)}) cap.release()这段代码跑起来后终端会实时打印每次进出的事件。LINE_Y这个参数需要根据你摄像头的实际安装角度来调整。具体做法是打开显示窗口观察一个人从画面下边缘走到画面中间的过程把虚拟线定在大概胸部以下的位置最好太低会把人从中部截断导致ID匹配困难太高则会让人一出现在画面边缘就被计数。4.4 数据落盘与异常场景容错生产环境中计数结果不能只打在终端里需要落盘保存。我经常用很朴素的做法按天生成CSV文件每条记录包含时间戳和方向文件名带上日期比如stream_log_20250117.csv。这种格式用Excel能开写后续的分析脚本也方便。另外有两个异常场景必须处理。一是有人站在虚拟线上来回踱步造成短时间内反复计数。处理思路是给每个ID加一个最近计数时间字段同一ID跨越虚拟线后在3秒内不允许再次计数。二是视频流断掉重连会丢失所有追踪ID如果恰好有人在画面中跨越了虚拟线就会漏计一次这是单节点方案无法完全避免的接受即可。5. 验证能否用的核心数据准确率是怎么测出来的5.1 一次小范围实测人工计数和系统计数对比方案写完之后不能只凭感觉说能用我专门安排了一次对比测试。场景选在某家临街小店门口摄像头挂在门内侧墙面上以大约45度的俯视角覆盖出入口通道。测试时长选了整整两个小时覆盖下午三点到五点包含光线较好的时段和太阳西斜逆光的时段。测试方法很简单一个人在屋里拿着机械计数器看到有人真正跨过门槛线就按一次分别记录进、出次数。系统这边同步记录。两个小时下来人工计数的结果是进去26人、出来22人合计48人次。系统记录的是进去25人、出来21人合计46人次其中有1个进的方向判断反了把出站的人记成了进。整体准确率约95.8%方向判断全部正确的比例约93.7%。这个结果对于看趋势、算转化率来说完全够用。两个举着手机挡在脸前快步走的人没有识别到这是那1人次漏计的直接原因后面我总结了原因和对策见下一节。5.2 影响准确率的因素清单与对策测试过程中我把影响准确率的因素尽量都记了一遍按重要程度排序如下。因素影响表现实际操作对策摄像头角度平视时前后遮挡严重两个人一起走会被算成一个人安装高度2.5米以上往下俯视斜角45度左右画面分辨率太高拖慢CPU处理速度太低小目标漏检CPU弱时用640x480即可光线剧烈变化逆光下深色衣服与背景融为一体容易丢检测固定摄像头曝光参数不要用自动曝光人流密度三人以上并排走时中心点追踪会串ID调大匹配距离同时接受一定误差虚拟线位置放太靠近画面边缘会提前触发计数放在画面纵向约2/3的位置人员移动速度跑过时两帧之间位移过大匹配断掉提高帧率到10FPS以上或者把匹配阈值调大这里单独说一下自动曝光的问题。很多人摄像头用默认设置一天之内画面亮度会被自动调节很多次每次调节都会导致检测置信度波动。在我测试的这个位置逆光时段自动曝光会突然提亮整个画面人脸区域直接过曝成白色。我在摄像头设置里手动固定了曝光值这个现象立刻缓解。5.3 长期无人值守时CPU性能和进程稳定性怎么保初次跑通之后我还让这套系统连续跑了将近一周没关机。这一周里暴露出的问题主要是长时间运行后CPU占用波动和内存持续缓慢增长。针对CPU占用核心手段是跳帧检测。我的做法是每隔一帧做一次模型推理比如视频流是15FPS模型推理只处理奇数帧偶数帧沿用上一帧的检测结果做展示。这样CPU占用能降下来而且几乎不影响计数准确性因为人跨越虚拟线这个动作通常会横跨好几帧不会只出现在被打掉的某一帧里。针对内存缓慢增长这是长时间跑目标检测程序的通病。排查起来费劲我的处理是干脆写了一个简单的定时任务每天凌晨四点让Python进程自动重启重启前把当天的计数结果保存到CSV重启后保证系统从头开始跑。一台设备连续跑一周进程重启十来次每一次重启耗时不到十秒对统计连续性几乎没有影响。另外建议打开一个CPU温度监控脚本旧电脑散热差长期满载运行如果温度超过85度就需要加装小风扇或者限制CPU负载。6. 跑通之后的扩展从单摄像头到多路视频流6.1 多路摄像头并行时的关键调整这套方案扩展起来也不难但麻烦主要在线程管理和资源分配上。最简单的做法是每个摄像头开一个Python线程每个线程独立跑自己的计数逻辑计数结果统一写入同一个SQLite数据库每条记录带上摄像头编号。多路并行时有一个容易被忽视的坑即使每路摄像头单独跑一个线程由于模型推理是CPU密集操作Python的GIL会导致多线程方案在纯CPU机器上几乎不加速。如果机器是多核的建议用multiprocessing替代threading每个摄像头一个进程占用不同的CPU核心。资源分配上我一般按单路推理占30%到40%的单核CPU来估算四核机器最多并行跑两路八核可以带四路。内存方面每路额外占用约1GB所以4GB内存的机器最多开两路。如果摄像头数量较多建议把输入分辨率降到480p或者换成更小的模型比如YOLOv8n改成YOLOv5nu。6.2 做一个极简的进店人数看板数据统计出来之后最好能有一个简单的展示页面。我用Flask写了一个不到两百行的Web服务从SQLite里读取当天的累计进店数、出店数和每小时的分布然后渲染成一个纯数字的页面直接扔到内网店里的手机浏览器就能打开。这里不建议一开始就上大而全的图表系统。客流统计的核心价值是趋势数据一个自动刷新的数字页就已经解决了大部分问题。如果确实想看折线图可以在页面里引入一个轻量级的开源图表库把按小时统计的数据画出来数据量不大时效果很直观。6.3 别忘了合规和隐私的底线最后必须提醒一点只要摄像头对着公共区域做长期录制或实时识别就涉及合规问题。实际部署时我通常在店铺入口处张贴一张本区域有人流统计分析摄像头的提示并且把摄像头调整到只能拍到门口通行区域避免对准收银台、更衣室这些敏感位置。数据也不要无限期保存建议只保留统计结果时间戳、方向而不是视频流本身统计结果定期清理。这既是负责任的工程做法也能避免后续很多麻烦。这套免费方案跑到现在我的体会是不要追求准确率无限接近百分之百那是在跟物理世界较劲。摄像头角度稍微好一点参数耐心调一遍九成以上的准确率已经能支撑绝大多数运营决策。我后来在多个类似小场景里复现这套流程越来越觉得它的价值不在于省了那几千块钱而在于数据完完全全握在自己手里想怎么分析、怎么扩展都是自己说了算。如果你是第一次尝试建议先用USB摄像头加笔记本跑通整个流程确认逻辑无误之后再考虑工业摄像头和服务器部署等哪天真需要升级往人体关键点识别或者更精准的轨迹追踪方向走也会有清晰的发力点。
阅读完成 · 觉得有帮助?
咨询建站