YOLOv8 区域计数系统:基于计算机视觉的智能物体计数方案
在计算机视觉领域,物体计数是一个常见且重要的任务。从交通流量监控到商场人流量统计,准确的物体计数能为决策提供关键数据支持。本文将介绍一个基于 YOLOv8 的区域计数系统,该系统允许用户在视频中定义可移动的计数区域,并实时统计进入这些区域的物体数量。
项目概述
这个区域计数系统结合了 YOLOv8 目标检测模型和 ByteTrack 跟踪算法,实现了在指定区域内对物体的准确计数。系统的核心特点包括:
- 支持自定义多边形或矩形计数区域
- 区域可通过鼠标拖动实时调整位置
- 基于跟踪算法的物体计数,避免重复计数
- 实时可视化显示计数结果
- 支持视频结果保存
该系统适用于各种需要在特定区域内进行物体计数的场景,如交通监控、商场人流分析、工业生产计数等。
技术栈介绍
这个区域计数系统基于以下技术和库构建:
- YOLOv8:Ultralytics 公司开发的新一代目标检测模型,具有高速度和高精度的特点
- Shapely:用于几何操作的 Python 库,用于定义和操作计数区域多边形
- OpenCV:开源计算机视觉库,用于视频处理和图像显示
- Numpy:用于数值计算和数据处理
- Argparse:用于命令行参数解析
核心功能解析
区域定义与管理
系统的核心是对计数区域的定义和管理。在代码中,counting_regions
列表存储了所有计数区域的信息:
python
counting_regions = [
{
'name': 'YOLOv8 Polygon Region',
'polygon': Polygon([(50, 80), (250, 20), (450, 80), (400, 350), (100, 350)]), # Polygon points
'counts': 0,
'dragging': False,
'region_color': (255, 42, 4), # BGR Value
'text_color': (255, 255, 255) # Region Text Color
},
{
'name': 'YOLOv8 Rectangle Region',
'polygon': Polygon([(200, 250), (440, 250), (440, 550), (200, 550)]), # Polygon points
'counts': 0,
'dragging': False,
'region_color': (37, 255, 225), # BGR Value
'text_color': (0, 0, 0), # Region Text Color
}, ]
每个区域包含以下信息:名称、多边形定义、计数、拖拽状态、区域颜色和文本颜色。系统支持任意数量的区域,每个区域可以是任意形状的多边形,包括矩形。
鼠标交互功能
系统实现了鼠标交互功能,允许用户通过鼠标拖动来移动计数区域:
python
def mouse_callback(event, x, y, flags, param):
"""Mouse call back event."""
global current_region
# 鼠标左键按下事件
if event == cv2.EVENT_LBUTTONDOWN:
for region in counting_regions:
if region['polygon'].contains(Point((x, y))):
current_region = region
current_region['dragging'] = True
current_region['offset_x'] = x
current_region['offset_y'] = y
# 鼠标移动事件
elif event == cv2.EVENT_MOUSEMOVE:
if current_region is not None and current_region['dragging']:
dx = x - current_region['offset_x']
dy = y - current_region['offset_y']
current_region['polygon'] = Polygon([
(p[0] + dx, p[1] + dy) for p in current_region['polygon'].exterior.coords])
current_region['offset_x'] = x
current_region['offset_y'] = y
# 鼠标左键释放事件
elif event == cv2.EVENT_LBUTTONUP:
if current_region is not None and current_region['dragging']:
current_region['dragging'] = False
当用户点击某个区域时,可以拖动该区域到新的位置,系统会实时更新区域的多边形坐标。
目标检测与跟踪
系统使用 YOLOv8 进行目标检测,并利用其跟踪功能进行物体跟踪:
python
# 提取结果
results = model.track(frame, persist=True, classes=classes)
if results[0].boxes.id is not None:
boxes = results[0].boxes.xyxy.cpu()
track_ids = results[0].boxes.id.int().cpu().tolist()
clss = results[0].boxes.cls.cpu().tolist()
# 绘制边界框和标签
annotator = Annotator(frame, line_width=line_thickness, example=str(names))
for box, track_id, cls in zip(boxes, track_ids, clss):
annotator.box_label(box, str(names[cls]), color=colors(cls, True))
# 计算边界框中心
bbox_center = (box[0] + box[2]) / 2, (box[1] + box[3]) / 2
# 跟踪历史记录
track = track_history[track_id]
track.append((float(bbox_center[0]), float(bbox_center[1])))
if len(track) > 30:
track.pop(0)
points = np.hstack(track).astype(np.int32).reshape((-1, 1, 2))
cv2.polylines(frame, [points], isClosed=False, color=colors(cls, True), thickness=track_thickness)
YOLOv8 的跟踪功能为每个检测到的物体分配一个唯一的跟踪 ID,track_history
字典记录每个物体的历史轨迹,用于绘制跟踪线和避免重复计数。
区域计数逻辑
核心的计数逻辑是检查每个检测到的物体是否位于某个计数区域内:
python
# 检查检测是否在区域内
for region in counting_regions:
if region['polygon'].contains(Point((bbox_center[0], bbox_center[1]))):
region['counts'] += 1
通过 Shapely 库的 contains
方法,系统可以高效地判断物体中心是否位于指定区域内。当物体进入区域时,对应区域的计数加一。
结果可视化
系统将检测结果、跟踪轨迹和计数区域可视化显示:
python
# 绘制区域(多边形/矩形)
for region in counting_regions:
region_label = str(region['counts'])
region_color = region['region_color']
region_text_color = region['text_color']
polygon_coords = np.array(region['polygon'].exterior.coords, dtype=np.int32)
centroid_x, centroid_y = int(region['polygon'].centroid.x), int(region['polygon'].centroid.y)
# 绘制计数文本
text_size, _ = cv2.getTextSize(region_label,
cv2.FONT_HERSHEY_SIMPLEX,
fontScale=0.7,
thickness=line_thickness)
text_x = centroid_x - text_size[0] // 2
text_y = centroid_y + text_size[1] // 2
cv2.rectangle(frame, (text_x - 5, text_y - text_size[1] - 5), (text_x + text_size[0] + 5, text_y + 5),
region_color, -1)
cv2.putText(frame, region_label, (text_x, text_y), cv2.FONT_HERSHEY_SIMPLEX, 0.7, region_text_color,
line_thickness)
# 绘制区域多边形
cv2.polylines(frame, [polygon_coords], isClosed=True, color=region_color, thickness=region_thickness)
每个区域的计数显示在区域中心位置,区域边界用指定颜色绘制,使结果直观易读。
使用指南
环境搭建
首先需要搭建必要的运行环境:
bash
# 创建虚拟环境(可选但推荐)
python -m venv env
source env/bin/activate # Linux/Mac
env\Scripts\activate # Windows
# 安装所需依赖
pip install ultralytics opencv-python numpy shapely
运行系统
系统通过命令行参数配置运行,以下是一个典型的运行示例:
bash
python region_counter.py --weights yolov8s.pt --source traffic_video.mp4 --view-img --save-img
命令行参数说明
系统支持以下命令行参数:
--weights
:模型权重路径,默认为 YOLOv8n 模型--source
:输入视频文件路径--device
:计算设备,可选 'cpu' 或 '0'(GPU)--view-img
:是否显示实时结果--save-img
:是否保存结果视频--classes
:指定检测的类别,如--classes 0 2
表示只检测汽车和卡车--line-thickness
:边界框线条粗细--track-thickness
:跟踪线粗细--region-thickness
:区域边界线粗细
自定义区域
默认配置中已经定义了两个示例区域,如需自定义区域,可修改代码中的 counting_regions
列表。每个区域由多边形顶点坐标定义,顶点坐标以像素为单位,格式为 (x, y)
。
自定义与扩展
更改区域形状和颜色
要更改区域的形状,只需修改 counting_regions
中对应区域的 polygon
定义。多边形顶点坐标按顺时针或逆时针顺序排列:
python
{
'name': '新区域',
'polygon': Polygon([(100, 100), (300, 100), (300, 300), (100, 300)]), # 矩形区域
'counts': 0,
'dragging': False,
'region_color': (0, 255, 0), # 绿色区域
'text_color': (255, 255, 255) # 白色文本
}
添加更多区域
如需添加更多计数区域,只需向 counting_regions
列表中添加新的区域定义:
python
counting_regions = [
# 已有的区域...
{
'name': '第三个区域',
'polygon': Polygon([(500, 100), (700, 100), (700, 200), (600, 300), (500, 200)]),
'counts': 0,
'dragging': False,
'region_color': (255, 0, 255), # 紫色区域
'text_color': (0, 0, 0) # 黑色文本
}
]
更换模型
系统默认使用 YOLOv8n 模型,如需更换为其他模型,只需修改 --weights
参数或代码中的 weights
默认值:
python
# 使用 YOLOv8s 模型
model = YOLO(f'{weights}')
过滤检测类别
如果只需要统计特定类别的物体,可以通过 --classes
参数指定:
bash
python region_counter.py --classes 2 3 # 只统计汽车、摩托车等类别
应用场景
这个区域计数系统在多个领域有实际应用价值:
交通监控与管理
- 十字路口交通流量统计
- 高速公路车辆计数与速度分析
- 停车场车位占用情况监测
商业与公共场合
- 商场、超市人流量统计与分析
- 展览会、演唱会入场人数统计
- 公共交通乘客流量监测
工业与农业
- 生产线产品计数与质量监控
- 养殖场动物数量统计
- 果园果实产量预估
安全与安防
- 边界区域入侵检测与计数
- 重要区域人员出入统计
- 公共场所拥挤程度监测
优化与改进方向
虽然当前系统已经实现了基本的区域计数功能,但还有许多可以优化和改进的地方:
- 计数逻辑优化:当前系统在每一帧都会重置计数,可改进为只在物体进入区域时计数,避免重复计数
- 方向识别:增加物体移动方向识别,实现只统计特定方向进入区域的物体
- 区域形状编辑:支持通过鼠标交互直接编辑区域形状,而不仅仅是移动区域
- 多摄像头支持:扩展系统以支持多个摄像头的同步计数
- 网络传输:添加网络传输功能,实现远程监控与计数
- 深度学习优化:针对特定应用场景重新训练模型,提高检测准确率
完整代码
python
import argparse
from collections import defaultdict
from pathlib import Path
import cv2
import numpy as np
from shapely.geometry import Polygon
from shapely.geometry.point import Point
from ultralytics import YOLO
from ultralytics.utils.files import increment_path
from ultralytics.utils.plotting import Annotator, colors
track_history = defaultdict(list)
current_region = None
counting_regions = [
{
'name': 'YOLOv8 Polygon Region',
'polygon': Polygon([(50, 80), (250, 20), (450, 80), (400, 350), (100, 350)]), # Polygon points
'counts': 0,
'dragging': False,
'region_color': (255, 42, 4), # BGR Value
'text_color': (255, 255, 255) # Region Text Color
},
{
'name': 'YOLOv8 Rectangle Region',
'polygon': Polygon([(200, 250), (440, 250), (440, 550), (200, 550)]), # Polygon points
'counts': 0,
'dragging': False,
'region_color': (37, 255, 225), # BGR Value
'text_color': (0, 0, 0), # Region Text Color
}, ]
def mouse_callback(event, x, y, flags, param):
"""Mouse call back event."""
global current_region
# Mouse left button down event
if event == cv2.EVENT_LBUTTONDOWN:
for region in counting_regions:
if region['polygon'].contains(Point((x, y))):
current_region = region
current_region['dragging'] = True
current_region['offset_x'] = x
current_region['offset_y'] = y
# Mouse move event
elif event == cv2.EVENT_MOUSEMOVE:
if current_region is not None and current_region['dragging']:
dx = x - current_region['offset_x']
dy = y - current_region['offset_y']
current_region['polygon'] = Polygon([
(p[0] + dx, p[1] + dy) for p in current_region['polygon'].exterior.coords])
current_region['offset_x'] = x
current_region['offset_y'] = y
# Mouse left button up event
elif event == cv2.EVENT_LBUTTONUP:
if current_region is not None and current_region['dragging']:
current_region['dragging'] = False
def run(
weights='yolov8n.pt',
source=None,
device='cpu',
view_img=False,
save_img=False,
exist_ok=False,
classes=None,
line_thickness=2,
track_thickness=2,
region_thickness=2,
):
"""
Run Region counting on a video using YOLOv8 and ByteTrack.
Supports movable region for real time counting inside specific area.
Supports multiple regions counting.
Regions can be Polygons or rectangle in shape
Args:
weights (str): Model weights path.
source (str): Video file path.
device (str): processing device cpu, 0, 1
view_img (bool): Show results.
save_img (bool): Save results.
exist_ok (bool): Overwrite existing files.
classes (list): classes to detect and track
line_thickness (int): Bounding box thickness.
track_thickness (int): Tracking line thickness
region_thickness (int): Region thickness.
"""
vid_frame_count = 0
# Check source path
if not Path(source).exists():
raise FileNotFoundError(f"Source path '{source}' does not exist.")
# Setup Model
model = YOLO(f'{weights}')
model.to('cuda') if device == '0' else model.to('cpu')
# Extract classes names
names = model.model.names
# Video setup
videocapture = cv2.VideoCapture(source)
frame_width, frame_height = int(videocapture.get(3)), int(videocapture.get(4))
fps, fourcc = int(videocapture.get(5)), cv2.VideoWriter_fourcc(*'mp4v')
# Output setup
save_dir = increment_path(Path('ultralytics_rc_output') / 'exp', exist_ok)
save_dir.mkdir(parents=True, exist_ok=True)
video_writer = cv2.VideoWriter(str(save_dir / f'{Path(source).stem}.mp4'), fourcc, fps, (frame_width, frame_height))
# Iterate over video frames
while videocapture.isOpened():
success, frame = videocapture.read()
if not success:
break
vid_frame_count += 1
# Extract the results
results = model.track(frame, persist=True, classes=classes)
if results[0].boxes.id is not None:
boxes = results[0].boxes.xyxy.cpu()
track_ids = results[0].boxes.id.int().cpu().tolist()
clss = results[0].boxes.cls.cpu().tolist()
annotator = Annotator(frame, line_width=line_thickness, example=str(names))
for box, track_id, cls in zip(boxes, track_ids, clss):
annotator.box_label(box, str(names[cls]), color=colors(cls, True))
bbox_center = (box[0] + box[2]) / 2, (box[1] + box[3]) / 2 # Bbox center
track = track_history[track_id] # Tracking Lines plot
track.append((float(bbox_center[0]), float(bbox_center[1])))
if len(track) > 30:
track.pop(0)
points = np.hstack(track).astype(np.int32).reshape((-1, 1, 2))
cv2.polylines(frame, [points], isClosed=False, color=colors(cls, True), thickness=track_thickness)
# Check if detection inside region
for region in counting_regions:
if region['polygon'].contains(Point((bbox_center[0], bbox_center[1]))):
region['counts'] += 1
# Draw regions (Polygons/Rectangles)
for region in counting_regions:
region_label = str(region['counts'])
region_color = region['region_color']
region_text_color = region['text_color']
polygon_coords = np.array(region['polygon'].exterior.coords, dtype=np.int32)
centroid_x, centroid_y = int(region['polygon'].centroid.x), int(region['polygon'].centroid.y)
text_size, _ = cv2.getTextSize(region_label,
cv2.FONT_HERSHEY_SIMPLEX,
fontScale=0.7,
thickness=line_thickness)
text_x = centroid_x - text_size[0] // 2
text_y = centroid_y + text_size[1] // 2
cv2.rectangle(frame, (text_x - 5, text_y - text_size[1] - 5), (text_x + text_size[0] + 5, text_y + 5),
region_color, -1)
cv2.putText(frame, region_label, (text_x, text_y), cv2.FONT_HERSHEY_SIMPLEX, 0.7, region_text_color,
line_thickness)
cv2.polylines(frame, [polygon_coords], isClosed=True, color=region_color, thickness=region_thickness)
if view_img:
if vid_frame_count == 1:
cv2.namedWindow('Ultralytics YOLOv8 Region Counter Movable')
cv2.setMouseCallback('Ultralytics YOLOv8 Region Counter Movable', mouse_callback)
cv2.imshow('Ultralytics YOLOv8 Region Counter Movable', frame)
if save_img:
video_writer.write(frame)
for region in counting_regions: # Reinitialize count for each region
region['counts'] = 0
if cv2.waitKey(1) & 0xFF == ord('q'):
break
del vid_frame_count
video_writer.release()
videocapture.release()
cv2.destroyAllWindows()
def parse_opt():
"""Parse command line arguments."""
parser = argparse.ArgumentParser()
parser.add_argument('--weights', type=str, default='E:/A/已完成/yolov8-car夜间车辆检测/train/yolov8s/weights/best.pt', help='initial weights path')
parser.add_argument('--device', default='cpu', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
parser.add_argument('--source', type=str, default='E:/A/已完成/yolov8-car夜间车辆检测/car.mp4', help='video file path')
parser.add_argument('--view-img', action='store_true',default=True , help='show results')
parser.add_argument('--save-img', action='store_true', default=True, help='save results')
parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')
parser.add_argument('--classes', nargs='+', type=int, help='filter by class: --classes 0, or --classes 0 2 3')
parser.add_argument('--line-thickness', type=int, default=2, help='bounding box thickness')
parser.add_argument('--track-thickness', type=int, default=2, help='Tracking line thickness')
parser.add_argument('--region-thickness', type=int, default=4, help='Region thickness')
return parser.parse_args()
def main(opt):
"""Main function."""
run(**vars(opt))
if __name__ == '__main__':
opt = parse_opt()
main(opt)
总结
本文介绍的基于 YOLOv8 的区域计数系统提供了一个灵活、易用的物体计数解决方案。系统结合了先进的目标检测和跟踪算法,实现了在指定区域内对物体的准确计数。通过简单的配置和交互,用户可以在各种场景中应用该系统进行物体计数。
随着计算机视觉技术的不断发展,这种基于深度学习的计数系统将在更多领域发挥重要作用。无论是交通管理、商业分析还是工业生产,准确的物体计数都能为决策提供有力的数据支持,帮助提高效率和优化资源配置。
如果你对这个项目感兴趣,可以根据实际需求进行修改和扩展,使其更好地满足特定场景的计数需求。