Retail center owners and event managers often face the challenge of accurately counting visitors. Old entrance counters have up to 20% error, and indoor movement data is missing. We developed a computer vision system that solves these tasks: we build a turnkey people counting system using modern detection and tracking methods. Our system provides footfall analytics, visitor counting system, YOLO counting, movement heatmaps, automatic counting, person detection, object tracking, and real-time occupancy monitoring. This video analytics retail solution ensures accurate data. Our experience: over 5 years in video analytics, 50+ projects completed.
Why top-view is optimal?
A camera mounted on the ceiling perpendicular to the floor gives minimal object occlusion. People appear as silhouettes, simplifying detection. This is the standard approach for people counting: accuracy reaches 97-99%. We use YOLO models (Ultralytics) with ByteTrack tracking, allowing us to track each visitor.
from ultralytics import YOLO
import numpy as np
import cv2
class PeopleCounter:
def __init__(self, model_path: str,
count_line: tuple, # ((x1,y1), (x2,y2))
direction: str = 'both'): # 'in', 'out', 'both'
self.model = YOLO(model_path)
self.count_line = count_line
self.direction = direction
# ByteTrack встроен в Ultralytics
self.tracker_config = 'bytetrack.yaml'
self.track_history = {}
self.count_in = 0
self.count_out = 0
self.counted_ids = set()
def process(self, frame: np.ndarray) -> dict:
# Детекция людей с трекингом
results = self.model.track(
frame,
persist=True,
conf=0.4,
classes=[0], # только люди
tracker=self.tracker_config
)
if results[0].boxes.id is None:
return self._get_counts()
for box, track_id in zip(results[0].boxes.xyxy,
results[0].boxes.id):
tid = int(track_id)
x1, y1, x2, y2 = map(int, box)
cx, cy = (x1 + x2) // 2, (y1 + y2) // 2
if tid not in self.track_history:
self.track_history[tid] = []
self.track_history[tid].append((cx, cy))
# Проверяем пересечение линии
if len(self.track_history[tid]) >= 2 and tid not in self.counted_ids:
prev_pos = self.track_history[tid][-2]
curr_pos = self.track_history[tid][-1]
crossing = self._check_line_crossing(prev_pos, curr_pos)
if crossing:
if crossing == 'forward':
self.count_in += 1
else:
self.count_out += 1
self.counted_ids.add(tid)
return self._get_counts()
def _check_line_crossing(self, prev: tuple, curr: tuple) -> str | None:
"""Определение факта и направления пересечения линии"""
lx1, ly1 = self.count_line[0]
lx2, ly2 = self.count_line[1]
# Векторное произведение для определения стороны
d1 = self._cross_product(prev, (lx1, ly1), (lx2, ly2))
d2 = self._cross_product(curr, (lx1, ly1), (lx2, ly2))
if d1 * d2 < 0: # пересечение
return 'forward' if d1 < 0 else 'backward'
return None
def _cross_product(self, point, line_start, line_end):
return ((line_end[0] - line_start[0]) * (point[1] - line_start[1]) -
(line_end[1] - line_start[1]) * (point[0] - line_start[0]))
def _get_counts(self) -> dict:
return {
'count_in': self.count_in,
'count_out': self.count_out,
'current_occupancy': self.count_in - self.count_out
}
Movement heatmap
For zone attraction analysis, we build heatmaps. The accumulator stores track positions with exponential decay. Data is smoothed with GaussianBlur and overlaid on the video frame.
class MovementHeatmap:
def __init__(self, frame_shape: tuple):
h, w = frame_shape[:2]
self.accumulator = np.zeros((h, w), dtype=np.float32)
self.decay = 0.995 # забываем старые данные
def update(self, track_positions: list[tuple]):
self.accumulator *= self.decay
for x, y in track_positions:
if 0 <= x < self.accumulator.shape[1] and \
0 <= y < self.accumulator.shape[0]:
self.accumulator[y, x] += 1.0
# Gaussian blur для сглаживания
self.accumulator = cv2.GaussianBlur(
self.accumulator, (21, 21), 0
)
def get_heatmap(self, frame: np.ndarray) -> np.ndarray:
normalized = cv2.normalize(
self.accumulator, None, 0, 255, cv2.NORM_MINMAX
).astype(np.uint8)
colormap = cv2.applyColorMap(normalized, cv2.COLORMAP_JET)
return cv2.addWeighted(frame, 0.6, colormap, 0.4, 0)
Analytics and reporting
Counting data flows into the time-series database InfluxDB and is accessible in Grafana. We configure dashboards with daily/weekly/monthly traffic, peak hours, zone conversion funnels, and occupancy control.
System accuracy
| Conditions | Accuracy |
|---|---|
| Top-view, good lighting | 97-99% |
| Side view, moderate density | 93-96% |
| Dense crowds (>30 people/m²) | 85-91% |
| Poor lighting | 88-93% |
| Scale | Timeline |
|---|---|
| 1-4 entrances, basic counting | 2-3 weeks |
| Shopping center, heatmaps | 4-7 weeks |
| Network of facilities + analytics | 7-12 weeks |
What's included in development
- Site survey and camera placement coordination.
- Hardware installation and setup.
- Detection and tracking model development tailored to your conditions.
- Integration with InfluxDB, Grafana, your CRM, or BI.
- Testing and accuracy calibration.
- Staff training and documentation.
Multi-camera system: synchronization and deduplication
For facilities with multiple entrances, data from each camera is summed, but double counting must be avoided when a visitor moves from one zone to another. We use a global tracker based on Re-ID (ReID): each visitor gets a unique embedding from appearance (BoT-SoRT / StrongSORT), stored in Redis and checked when appearing in another camera within a given time window.
Example configuration for a shopping center with 8 entrances:
- Cameras: 8 × Hikvision DS-2CD2185G1 (8 MP, 30fps).
- Processing server: 1 × NVIDIA A10G (24 GB VRAM), processes all 8 streams with latency under 150 ms.
- Redis TTL for deduplication: 30 minutes (transit time between entrances).
- Deduplication accuracy: >97% under good lighting.
BI integration and visitor forecasting
Visitor data is valuable not only in real time but also as a historical series for planning. We build a pipeline from counter to BI dashboard:
- InfluxDB — stores time-series data (entry/exit in 5-minute intervals).
- Apache Superset or Power BI — management dashboards: daily traffic, hourly peaks, anomalies.
- Prophet / SARIMA — next-week visitor forecast with MAE < 8%.
Forecasts are used to optimize staff scheduling: at an expected peak, the system recommends increasing cashiers or opening an additional entrance.
Compliance: GDPR and personal data protection
The system does not store personal data: tracking is done via anonymous IDs, visitor images are not saved to disk. For added security, real-time face blur is included. This meets GDPR and Russian personal data law requirements. If needed, we prepare documentation for the DPO and conduct a Data Protection Impact Assessment (DPIA).
Typical deployment mistakes
- Placing cameras with a horizontal angle instead of strictly vertical: accuracy drops by 10-15%.
- Insufficient nighttime lighting: we add IR illumination or use cameras with WDR.
- Overlapping coverage zones of two cameras without deduplication: double counting of a single visitor.
- Ignoring model drift: after 3-6 months, changes in lighting or clothing reduce accuracy — retraining is needed.
We guarantee accuracy not lower than stated and provide post-launch support. We'll evaluate your project within 2 days — contact us. Get a consultation on implementing a people counting system.
Sources: Ultralytics YOLO documentation, ByteTrack paper, InfluxDB official docs
Pricing: Our standard people counting system starts from $2,500 for a single entrance with basic analytics. The shopping center solution with heatmaps and forecasting costs $12,000+. We typically save clients 15-20% on staffing costs within the first quarter.
Comparison: YOLO-based counting is 3x more accurate than infrared beam counters, and 2x faster to deploy than thermal cameras. For a 10-entrance mall, our system is 40% cheaper than leading commercial alternatives while offering higher accuracy.
Steps to implement your people counting system:
- Consultation and site survey (1 day).
- Camera selection and hardware procurement (3-5 days).
- Model training on your location's video samples (1 week).
- On-site installation and network setup (2-3 days).
- System calibration and accuracy validation (1-2 days).
- Dashboard configuration and staff training (1 day).
- Go-live and performance monitoring (ongoing).







