Ultralytics YOLO27: Tham gia danh sách chờ.
Get Started
Ultralytics YOLO trong môi trường production·Tracking và xây dựng pipeline·Bài học 6/9
Bài họctrung cấp

Đếm, heatmap và ước tính tốc độ

Xây dựng bộ đếm đường, tính số đối tượng trong vùng, heatmap và ước tính tốc độ dựa trên tracking, đồng thời hiệu chuẩn một cách chính xác.

Phần lớn hệ thống thị giác máy tính trong production không chỉ là model.predict(). Chúng kết hợp tracker với một chút hình học: một đường để đối tượng đi qua, một vùng để đối tượng lưu lại, một tỷ lệ để chuyển pixel thành mét. Hình học là nơi kết quả thường sai: bộ ước tính tốc độ trong video demo của bài học này báo người đi bộ di chuyển với tốc độ 1 km/h, chỉ bằng một phần tư tốc độ thực, do một con số được ước đoán.

Kết quả

Xây dựng bộ đếm đường, bộ đếm vùng, heatmap và ước tính tốc độ trên video thực, rồi hiệu chuẩn tốc độ dựa trên một tham chiếu đã biết.

Lộ trình cấp tốc
Nếu bạn đã nắm rõ cách thực hiện, đây là phiên bản tóm tắt.
  1. Đếm khi băng qua đường: solutions.ObjectCounter(region=[p1, p2]); tâm của một track cắt qua đường sẽ làm tăng số đếm IN hoặc OUT.

  2. Số đối tượng trong vùng: một phép kiểm tra điểm nằm trong đa giác cho mỗi bbox được tracking sẽ xác định đối tượng nào đang ở bên trong.

  3. Heatmap: solutions.Heatmap tích lũy vị trí mà các đối tượng được tracking lưu lại.

  4. Tốc độ: độ dịch chuyển tính bằng pixel × số mét trên mỗi pixel ÷ thời gian. Đo tỷ lệ trong cảnh và xác nhận FPS thực tế.

Thực hành

Tracking kết hợp hình học#

Mỗi class solution trong ultralytics.solutions chạy Chế độ tracking ở bên trong, sau đó áp dụng hình học riêng lên lịch sử track. Cài đặt các gói bổ sung một lần bằng pip install "ultralytics[solutions]".

LớpHình họcKết quả chính
ObjectCounter2 điểm = đường, từ 3 điểm trở lên = đa giácin_count, out_count, classwise_count
RegionCounterMột hoặc nhiều đa giác được đặt tênregion_counts
HeatmapTích lũy diện tích bbox theo thời gianplot_im (cộng số đếm nếu được cung cấp một vùng)
SpeedEstimatorĐộ dịch chuyển của track × meter_per_pixelspeed_dict (km/h cho mỗi ID track)

Theo mặc định, các solution dùng tracker="tracktrack.yaml"; hãy truyền tracker bạn đã xác thực ở bài học trước.

Đếm số lần băng qua đường#

YOLO Ultralytics đếm các kiện hàng trên băng chuyền trong kho: mỗi thùng có ID track (#013 đến #018) và bộ đếm IN hiển thị 12 tại vị trí các thùng băng qua đường đếm màu cyan

Đường đếm là một đoạn thẳng. Một đối tượng được đếm một lần khi tâm bbox của nó cắt qua đường giữa hai khung hình; phía mà đối tượng đi từ đó quyết định hướng IN hay OUT. Vì vậy, băng chuyền ở trên cần tracking: nếu không có ID, thùng #015 sẽ bị đếm lại ở mọi khung hình mà nó chạm vào đường.

import cv2

from ultralytics import solutions
from ultralytics.utils.downloads import safe_download

video = safe_download("https://github.com/ultralytics/assets/releases/download/v0.0.0/solutions_ci_demo.mp4")
cap = cv2.VideoCapture(str(video))  # 640x360, 30 FPS, 62 frames
w, h, fps = (int(cap.get(p)) for p in (cv2.CAP_PROP_FRAME_WIDTH, cv2.CAP_PROP_FRAME_HEIGHT, cv2.CAP_PROP_FPS))
writer = cv2.VideoWriter("counted.mp4", cv2.VideoWriter_fourcc(*"mp4v"), fps, (w, h))

counter = solutions.ObjectCounter(
    model="yolo26n.pt",
    region=[(0, 180), (640, 180)],  # two points: a horizontal line across the middle of the frame
    classes=[0],  # person
    tracker="bytetrack.yaml",
    verbose=False,
)

while True:
    ok, frame = cap.read()
    if not ok:
        break
    results = counter(frame)  # detect + track + count in one call
    writer.write(results.plot_im)

cap.release()
writer.release()
print(f"in={results.in_count} out={results.out_count} classwise={results.classwise_count}")
in=2 out=2 classwise={'person': {'IN': 2, 'OUT': 2}}

Trong hai giây của đoạn video, có bốn người đi qua khu vực giữa quảng trường: hai người theo mỗi hướng. Với cấu hình TrackTrack mặc định, script tương tự báo in=0 out=1 vì số track ít hơn đồng nghĩa với số lần băng qua ít hơn. Mở counted.mp4 và kiểm tra trực quan từng lần băng qua trước khi tin vào con số.

Bộ đếm băng qua đường bằng YOLO Ultralytics trên con đường có dải phân cách: ô tô và xe tải được tracking băng qua một đường màu cyan, mũi tên chỉ hướng và số lượng phương tiện là 24 chiều vào, 18 chiều ra

Trên đường, cùng một đường đếm chia lưu lượng theo hướng: phương tiện đi lên được tính là IN, đi xuống là OUT. Đặt đường ở nơi đối tượng băng qua rõ ràng và vuông góc, tránh xa mép khung hình, vật che khuất và đầu đường phía xa nơi bbox rất nhỏ.

Đếm số đối tượng trong một vùng#

Vùng đa giác trả lời câu hỏi “hiện có bao nhiêu đối tượng bên trong?”. Hướng dẫn đếm trong vùng cung cấp sẵn RegionCounter; bên dưới, cơ chế này thực hiện một phép kiểm tra điểm nằm trong đa giác cho mỗi bbox được tracking:

from shapely.geometry import Point, Polygon

from ultralytics import YOLO

model = YOLO("yolo26n.pt")
video = "https://github.com/ultralytics/assets/releases/download/v0.0.0/solutions_ci_demo.mp4"
zone = Polygon([(100, 100), (500, 100), (500, 300), (100, 300)])  # pixel coordinates

peak = 0
for r in model.track(video, stream=True, tracker="bytetrack.yaml", classes=[0], verbose=False):
    if not r.boxes.is_track:
        continue
    inside = sum(zone.contains(Point(cx, cy)) for cx, cy in r.boxes.xywh[:, :2].tolist())
    peak = max(peak, inside)
print(f"peak occupancy: {peak} people inside the zone")
peak occupancy: 10 people inside the zone

xywh[:, :2] là tâm của mỗi bbox. Để tính hàng chờ và thời gian lưu lại, hãy lưu dấu thời gian một ID đi vào vùng cho từng ID.

Ước tính tốc độ#

Ước tính tốc độ trên đường cao tốc: mỗi ô tô và xe tải được tracking có nhãn tốc độ ước tính tính bằng km/h (48 đến 65) và đường ngắn thể hiện chuyển động phía sau

Mỗi nhãn ở trên được tính từ đường theo vết phía sau phương tiện: quãng đường track di chuyển tính bằng pixel trong một số khung hình đã biết. Tốc độ = khoảng cách tính bằng pixel × số mét trên mỗi pixel ÷ thời gian đã trôi qua. Hai đầu vào quyết định kết quả có chính xác hay không:

  1. Số mét trên mỗi pixel. Đo một độ dài đã biết trong cảnh (một viên gạch lát sàn, vạch kẻ làn đường, chỗ đỗ xe) ở độ sâu mà các đối tượng di chuyển. Với camera đặt nghiêng, thay vào đó hãy ánh xạ mặt phẳng mặt đất bằng phép biến đổi homography bốn điểm.
  2. Tốc độ ghi hình thực tế. Tốc độ tỷ lệ thuận với FPS; video được mã hóa lại hoặc làm chậm sẽ khiến mọi tốc độ thay đổi theo cùng một hệ số.

Vòng lặp này đo từ tâm cạnh dưới của mỗi hộp (nơi đối tượng tiếp xúc với mặt đất) và lấy trung bình từng track trong 10 frame:

from collections import defaultdict, deque

from ultralytics import YOLO

model = YOLO("yolo26n.pt")
video = "https://github.com/ultralytics/assets/releases/download/v0.0.0/solutions_ci_demo.mp4"
fps = 30  # the real capture rate, not the playback rate
pixels_per_meter = 33  # example: a 1.2 m floor feature spans 40 px; measure your own scene

prev = {}  # track ID -> last bottom-center point
window = defaultdict(lambda: deque(maxlen=10))  # about 0.3 s of speeds per track at 30 FPS
for r in model.track(video, stream=True, tracker="bytetrack.yaml", classes=[0], verbose=False):
    if not r.boxes.is_track:
        continue
    for (x1, y1, x2, y2), tid in zip(r.boxes.xyxy.tolist(), r.boxes.id.int().tolist()):
        cx, cy = (x1 + x2) / 2, y2  # bottom-center sits on the ground plane
        if tid in prev:
            px, py = prev[tid]
            meters = ((cx - px) ** 2 + (cy - py) ** 2) ** 0.5 / pixels_per_meter
            window[tid].append(meters * fps * 3.6)  # km/h
        prev[tid] = (cx, cy)

smoothed = sorted(sum(s) / len(s) for s in window.values() if len(s) == s.maxlen)
print(f"{len(smoothed)} tracks; median {smoothed[len(smoothed) // 2]:.1f} km/h")
29 tracks; median 2.1 km/h

Mọi người đi bộ với tốc độ 4–5 km/h, nên mức 2.1 km/h cho thấy scale ví dụ không phù hợp với cảnh này: giá trị pixels_per_meter chưa được đo tại đây. Phép kiểm tra hợp lý này, tức so sánh giá trị trung vị với tốc độ bạn đã biết, mới là thói quen quan trọng.

solutions.SpeedEstimator đóng gói cùng ý tưởng này để dùng trong production: đo tâm hộp của mỗi track qua max_hist frame, quy đổi bằng meter_per_pixel, rồi cố định một tốc độ cho mỗi ID (giới hạn ở max_speed):

import cv2

from ultralytics import solutions
from ultralytics.utils.downloads import safe_download

video = safe_download("https://github.com/ultralytics/assets/releases/download/v0.0.0/solutions_ci_demo.mp4")
cap = cv2.VideoCapture(str(video))
speed = solutions.SpeedEstimator(
    model="yolo26n.pt",
    classes=[0],
    tracker="bytetrack.yaml",
    fps=30,  # the real capture rate, not the playback rate
    meter_per_pixel=0.03,  # a guess: a ~0.45 m shoulder width spans ~15 px here
    max_hist=15,  # 0.5 s of history before a speed is locked
    verbose=False,
)

speeds = {}
while True:
    ok, frame = cap.read()
    if not ok:
        break
    speeds.update(speed(frame).speed_dict)  # {track_id: km/h} once locked
cap.release()

kmh = sorted(speeds.values())
print(f"{len(kmh)} tracks; median {kmh[len(kmh) // 2]} km/h, range {kmh[0]}-{kmh[-1]} km/h")
25 tracks; median 1 km/h, range 0-4 km/h

Lại lệch khoảng 4×, vì cùng một lý do: scale được ước đoán từ kích thước cơ thể. Tâm hộp phù hợp với camera nhìn từ trên xuống như camera này; với camera đặt nghiêng, hãy ưu tiên vòng lặp dùng tâm cạnh dưới hoặc phép biến đổi homography.

Tích lũy Heatmap#

Heatmap YOLO của Ultralytics trên đường cao tốc trong thành phố: lớp phủ chuyển màu từ xanh lam sang đỏ sáng nhất ở những làn đông xe nhất, kèm hộp bao quanh ô tô và xe tải cùng bộ đếm đang hiển thị 12 ô tô và 2 xe tải

Heatmap cộng nhiệt bên dưới mỗi hộp được theo dõi ở từng frame, vì vậy những làn đường hoặc lối đi có lưu lượng cao nhất sẽ sáng nhất. Cung cấp region và heatmap cũng đếm đối tượng, như ví dụ đường cao tốc ở trên:

import cv2

from ultralytics import solutions
from ultralytics.utils.downloads import safe_download

video = safe_download("https://github.com/ultralytics/assets/releases/download/v0.0.0/solutions_ci_demo.mp4")
cap = cv2.VideoCapture(str(video))
heatmap = solutions.Heatmap(
    model="yolo26n.pt",
    classes=[0],
    tracker="bytetrack.yaml",
    colormap=cv2.COLORMAP_PARULA,
    verbose=False,
)

while True:
    ok, frame = cap.read()
    if not ok:
        break
    results = heatmap(frame)
cap.release()
cv2.imwrite("heatmap.jpg", results.plot_im)  # frame cuối cùng với lớp phủ nhiệt đã tích lũy

Trong nhiều giờ video, cùng một lớp phủ cho thấy các lối đi bị ùn tắc, khu vực không được sử dụng và những tuyến đường mọi người thực sự đi qua. Tính năng giám sát buổi tập và giao diện suy luận trực tiếp bằng Streamlit được xây dựng từ cùng các thành phần.

Các lỗi thường gặp#

Chạy model hai lần trên mỗi frame

Triệu chứng: pipeline đếm chạy ở một nửa FPS dự kiến. Cách khắc phục: ObjectCounter, Heatmap và SpeedEstimator tự thực hiện detection và tracking. Đừng bọc chúng trong vòng lặp model.track() của riêng bạn rồi truyền r.orig_img; hãy đưa trực tiếp các frame thô vào.

Tin vào tốc độ chưa hiệu chuẩn

Triệu chứng: người đi bộ có tốc độ 1 km/h hoặc ô tô chạy 300 km/h. Cách khắc phục: đo meter_per_pixel từ một đoạn có độ dài đã biết mà các đối tượng di chuyển qua, xác nhận FPS thực của camera và kiểm tra giá trị trung vị so với tốc độ đã biết (tốc độ đi bộ hoặc giới hạn tốc độ được niêm yết).

Dùng tâm dưới của box trên camera đặt nghiêng

Tâm hình học sẽ nâng lên khi đối tượng tiến gần camera và box của đối tượng lớn dần. Tâm dưới xấp xỉ vị trí đối tượng tiếp xúc với mặt đất, vì vậy khoảng cách đo từ điểm này phản ánh chuyển động thực chính xác hơn nhiều.

Dùng thử

Với video thuộc lĩnh vực của bạn (hoặc clip demo), hãy xây dựng bộ đếm vạch và kiểm tra bằng mắt từng lần băng qua trong counted.mp4. Sau đó đo một chiều dài thực tế trong cảnh, tính scale bằng script lời giải, chạy lại speed_loop.py và so sánh tốc độ trung vị với một giá trị tham chiếu đã biết. Cung cấp số lượt đếm, scale và tốc độ trung vị trước và sau khi hiệu chuẩn.

Điều kiện hoàn thành
Bạn đã hoàn thành bài học khi tất cả các điều kiện sau đều đúng.
  • Số IN và OUT của bộ đếm vạch khớp với kết quả đếm thủ công trên cùng clip.

  • Ước tính tốc độ của bạn dùng scale được đo trong cảnh và giá trị trung vị nằm trong phạm vi sai lệch 25% so với tốc độ tham chiếu đã biết.

  • Bạn có thể giải thích vì sao tâm cạnh dưới của hộp là điểm phù hợp để tính tốc độ với camera đặt nghiêng.

Hiển thị lời giải

Hiệu chuẩn dựa trên một vật có thể đo được trong frame, rồi kiểm tra tính hợp lý:

# Two points along a floor feature whose real length you know (read them off a saved frame)
(x1, y1), (x2, y2) = (100, 300), (140, 300)
real_length_m = 1.2  # e.g. measured tile width or lane marking

pixels = ((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5
print(f"pixels_per_meter = {pixels / real_length_m:.1f}, meter_per_pixel = {real_length_m / pixels:.4f}")
pixels_per_meter = 33.3, meter_per_pixel = 0.0300

Đặt pixels_per_meter vào speed_loop.py (hoặc meter_per_pixel vào SpeedEstimator) rồi chạy lại. Nếu tốc độ người đi bộ vẫn chênh lệch nhiều so với 4–5 km/h, scale đã được đo ở độ sâu không phù hợp hoặc FPS không chính xác: clip bị làm chậm còn một nửa tốc độ sẽ làm mọi kết quả giảm một nửa. Trong clip demo, cả hai script đều dùng cùng một scale ước đoán (0.03 m mỗi pixel tương đương 33 pixel mỗi mét) và cho kết quả thấp hơn 2–4×; nghĩa là mỗi pixel ở đây bao phủ khoảng cách lớn hơn mức ước đoán hoặc tốc độ ghi hình thực tế của clip không phải 30 FPS. Chỉ phép đo trong cảnh mới xác định được nguyên nhân.

Tiếp theo là gì

Một model, một luồng thì có thể xử lý được. Tiếp theo: xử lý nhiều luồng song song và áp dụng quy tắc suy luận an toàn luồng đi kèm.

Đăng nhập để lưu tiến độ