Ultralytics YOLO27: انضم إلى قائمة الانتظار.
Get Started
Ultralytics YOLO في بيئة الإنتاج·التتبّع وبناء مسارات المعالجة·الدرس 6/9
الدرسمتوسط

الإحصاء والخرائط الحرارية وتقدير السرعة

أنشئ عدّادات للخطوط، وإحصاءات لإشغال المناطق، وخرائط حرارية، وتقديرات للسرعة بالاعتماد على التتبّع، وعايرها بواقعية.

معظم أنظمة الرؤية في بيئات الإنتاج ليست model.predict()، بل تتكوّن من متتبّع وبعض العمليات الهندسية: خط تعبره الأجسام، أو منطقة تشغلها، أو مقياس يحوّل البكسلات إلى أمتار. وتكمن أسباب الخطأ في النتائج في العمليات الهندسية: إذ يقدّر مقياس السرعة في مقطع العرض التوضيحي لهذا الدرس سرعة المشاة على أنها 1 كم/ساعة، أي ربع سرعتهم الفعلية، بسبب رقم واحد جرى تخمينه.

النتيجة

أنشئ عدّادًا لعبور الخطوط، وإحصاءً للمنطقة، وخريطة حرارية، وتقديرًا للسرعة باستخدام فيديو حقيقي، وعاير السرعة بالاستناد إلى مرجع معروف.

المسار السريع
إذا كنت تعرف الأساسيات بالفعل، فإليك النسخة المختصرة.
  1. عبور الخط: solutions.ObjectCounter(region=[p1, p2])؛ ويؤدي عبور مركز المسار للخط إلى إضافة العدد إلى IN أو OUT.

  2. إشغال المنطقة: يحدد اختبار واحد للنقطة داخل المضلع لكل صندوق متتبّع مَن يوجد داخل المنطقة حاليًا.

  3. الخريطة الحرارية: يجمع solutions.Heatmap بيانات الأماكن التي تقضي فيها الأجسام المتتبّعة وقتها.

  4. السرعة: الإزاحة بالبكسل × الأمتار لكل بكسل ÷ الزمن. قِس المقياس في المشهد وتأكد من معدل الإطارات الفعلي.

عملي

التتبّع مع العمليات الهندسية#

تشغّل كل فئة حلول في ultralytics.solutions وضع التتبّع داخليًا، ثم تطبّق عملياتها الهندسية الخاصة على سجلّ المسار. ثبّت الإضافات مرة واحدة باستخدام pip install "ultralytics[solutions]".

الفئةالعمليات الهندسيةالنتائج الأساسية
ObjectCounterنقطتان = خط، و3 نقاط أو أكثر = مضلعin_count, out_count, classwise_count
RegionCounterمضلع واحد أو أكثر بأسماء محددةregion_counts
Heatmapيجمع مساحات الصناديق عبر الزمنplot_im (بالإضافة إلى الإحصاء عند تحديد منطقة)
SpeedEstimatorإزاحة المسار × meter_per_pixelspeed_dict (كم/ساعة لكل معرّف مسار)

تستخدم الحلول tracker="tracktrack.yaml" افتراضيًا؛ مرّر المتتبّع الذي تحققت من صلاحيته في الدرس السابق.

إحصاء عبور الخطوط#

إحصاء الطرود على سير ناقل في مستودع باستخدام Ultralytics YOLO: يحمل كل صندوق معرّف مسار (من #013 إلى #018)، ويعرض عدّاد IN العدد 12 عند عبور الصناديق خط الإحصاء السماوي

خط الإحصاء هو قطعة مستقيمة. يُحصى الجسم مرة واحدة عند عبور مركز صندوقه الخط بين إطارين، ويحدد الجانب الذي أتى منه ما إذا كان يُضاف إلى IN أو OUT. لذلك يتطلب سير النقل أعلاه التتبّع: فمن دون المعرّف، سيُحصى الصندوق #015 مجددًا في كل إطار يلامس فيه الخط.

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}}

عبر أربعة أشخاص منتصف الساحة خلال ثانيتين في المقطع: اثنان في كل اتجاه. باستخدام إعداد TrackTrack الافتراضي، يعرض السكربت نفسه in=0 out=1 لأن انخفاض عدد المسارات يعني انخفاض عدد مرات العبور. افتح counted.mp4 وتحقق بصريًا من كل عبور قبل الوثوق بأي رقم.

عدّاد عبور الخطوط باستخدام Ultralytics YOLO على طريق مقسوم: تعبر سيارات وشاحنة متتبّعة خطًا سماويًا، مع أسهم تشير إلى الاتجاه وعدد المركبات: 24 إلى الداخل و18 إلى الخارج

على الطريق، يقسم الخط نفسه حركة المرور حسب الاتجاه: تُحتسب المركبات التي تعبر إلى الأعلى ضمن IN، والتي تعبر إلى الأسفل ضمن OUT. ضع الخط في موضع تعبره فيه الأجسام بوضوح وبزاوية قائمة، بعيدًا عن حواف الإطار والعوائق والطرف البعيد من الطريق حيث تكون الصناديق صغيرة.

إحصاء الإشغال في منطقة#

يجيب المضلع الذي يحدد المنطقة عن السؤال: «كم عدد الأجسام داخل المنطقة الآن؟» يتضمن دليل عدّ المناطق عنصرًا جاهزًا هو RegionCounter؛ وتتم العملية في أساسها باختبار واحد للنقطة داخل المضلع لكل صندوق متتبّع:

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] هو مركز كل صندوق. ولإحصاء الطوابير ومدة المكوث، احتفظ بطابع زمني لكل معرّف يحدد وقت دخوله المنطقة.

تقدير السرعة#

تقدير السرعة على طريق سريع: تحمل كل سيارة وشاحنة متتبّعة تسمية بسرعتها المقدّرة بوحدة كم/ساعة (من 48 إلى 65)، ويتبعها خط قصير لمسار حركتها

تأتي كل تسمية أعلاه من المسار خلف المركبة: المسافة التي قطعها مسارها بالبكسل خلال عدد معلوم من الإطارات. السرعة = المسافة بالبكسل × الأمتار لكل بكسل ÷ الزمن المنقضي. ويحدد مدخلان مدى صحة النتيجة:

  1. الأمتار لكل بكسل. قِس طولًا معلومًا في المشهد (بلاطة أرضية، أو علامة مسار، أو موقف سيارات) عند العمق الذي تتحرك فيه الأجسام. أما في حالة الكاميرات المائلة، فاستخدم تحويلًا متجانسًا من أربع نقاط لرسم مستوى الأرض بدلًا من ذلك.
  2. معدل الالتقاط الفعلي. تتناسب السرعة طرديًا مع FPS؛ إذ تُضرب جميع قيم السرعة في العامل نفسه عند إعادة ترميز اللقطات أو إبطائها.

تقيس هذه الحلقة من منتصف أسفل كل صندوق (حيث يلامس الجسم الأرض) وتحسب متوسط كل مسار على مدى 10 إطارات:

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

يمشي الناس بسرعة 4–5 km/h، لذا فإن 2.1 km/h تعني أن مقياس المثال غير صحيح لهذا المشهد: لم تُقَس قيمة pixels_per_meter هنا. وهذا التحقق المعقول، أي مقارنة الوسيط بسرعة تعرفها، هو العادة المهمة.

تُطبّق solutions.SpeedEstimator الفكرة نفسها في بيئة الإنتاج: فهي تقيس مركز صندوق كل مسار على مدى max_hist إطارًا، وتحوله باستخدام meter_per_pixel، وتثبّت سرعة واحدة لكل معرّف (بحد أقصى 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

ما زالت القيمة أقل بنحو 4×، للسبب نفسه: مقياس مُخمّن بناءً على حجم الجسم. مركز الصندوق مناسب للكاميرات العلوية مثل هذه؛ أما مع الكاميرات المائلة، ففضّل حلقة منتصف أسفل الصندوق أو التحويل الإسقاطي.

تجميع خريطة حرارية#

خريطة حرارية من Ultralytics YOLO لحركة المرور على طريق سريع في المدينة: تراكب لوني من الأزرق إلى الأحمر يبلغ أقصى سطوعه في المسارات الأكثر ازدحامًا، مع صناديق للسيارات والشاحنات وعدّاد يعرض 12 سيارة وشاحنتين

تضيف الخريطة الحرارية حرارة أسفل كل صندوق متتبَّع في كل إطار، فتتوهج المسارات أو الممرات التي تشهد أكبر حركة مرور بأقصى سطوع. امنحها region وستعدّ الكائنات أيضًا، كما في الطريق السريع أعلاه:

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)  # ⁨الإطار الأخير مع تراكب الحرارة المتراكمة⁩

على مدى ساعات من اللقطات، يُظهر التراكب نفسه الممرات المزدحمة والمناطق غير المستخدمة والمسارات التي يسلكها الأشخاص فعلًا. ويُبنى كل من مراقبة التمارين وواجهة الاستدلال المباشر باستخدام Streamlit من اللبنات نفسها.

مواطن الخلل#

تشغيل النموذج مرتين لكل إطار

العَرَض: تعمل معالجة عدّ الإطارات بنصف FPS المتوقع. الحل: تنفّذ ObjectCounter وHeatmap وSpeedEstimator الكشف والتعقب داخليًا. لا تضعها ضمن حلقة model.track() خاصة بك ولا تمرّر إليها r.orig_img؛ بل زوّدها بإطارات خام.

الوثوق بسرعة غير معايرة

العَرَض: تبلغ سرعة المشاة 1 km/h والسيارات 300 km/h. الحل: قِس meter_per_pixel انطلاقًا من طول معروف تتحرك الأجسام على امتداده، وتأكد من FPS الفعلي للكاميرا، وقارن الوسيط بسرعة معروفة (سرعة المشي أو الحد المعلن للسرعة).

استخدم مركز أسفل الصندوق مع الكاميرات المائلة

يرتفع المركز الهندسي كلما اقترب جسم من الكاميرا وكبر صندوقه. ويقدّر المركز السفلي موضع ملامسة الجسم للأرض، لذا تتبع المسافات المقاسة منه الحركة الفعلية بدقة أكبر بكثير.

جرّبه

استخدم فيديو من مجالك (أو المقطع التجريبي)، وأنشئ عدّادًا للخطوط، ثم تحقّق بصريًا من كل عملية عبور في counted.mp4. بعد ذلك، قِس طولًا واحدًا في العالم الحقيقي داخل المشهد، واحسب المقياس باستخدام سكربت الحل، وأعِد تشغيل speed_loop.py، ثم قارن وسيط السرعة بمرجع معروف. قدّم الأعداد والمقياس ووسيط السرعات قبل المعايرة وبعدها.

متى يُعدّ مكتملًا
تكون قد أتممت الدرس عند تحقق جميع الشروط التالية.
  • يتطابق عدد مرات الدخول والخروج في عدّاد الخطوط مع العدّ اليدوي للمقطع نفسه.

  • يستخدم تقدير السرعة مقياسًا قيس في المشهد، ويقع وسيطه ضمن 25% من سرعة مرجعية معروفة.

  • يمكنك توضيح سبب كون منتصف أسفل الصندوق النقطة المناسبة لقياس السرعة باستخدام كاميرا مائلة.

عرض الحل

عاير باستخدام شيء يمكنك قياسه في الإطار، ثم تحقّق من معقولية النتيجة:

# 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

ضع pixels_per_meter في speed_loop.py (أو meter_per_pixel في SpeedEstimator) ثم أعد التشغيل. إذا ظلت سرعة المشاة بعيدة عن 4–5 km/h، فقد قيس المقياس عند عمق غير مناسب أو أن قيمة FPS غير صحيحة: فإبطاء المقطع إلى نصف سرعته يخفض كل قراءة إلى النصف. في المقطع التجريبي، استخدم كلا السكربتين المقياس المُخمّن نفسه (0.03 m لكل بكسل تعادل 33 بكسلًا لكل متر)، وكانت القراءات أقل بمقدار 2–4×؛ لذا إما أن البكسل هنا يغطي مسافة أكبر مما افترضه التخمين، أو أن معدل الالتقاط الفعلي للمقطع ليس 30 FPS. ولا يحسم الأمر إلا القياس داخل المشهد.

ما الخطوة التالية

يمكن التعامل مع نموذج واحد وتدفق واحد. التالي: تدفقات كثيرة بالتوازي، وما يصاحبها من انضباط في الاستدلال الآمن للخيوط.

سجّل الدخول لحفظ التقدم