A computer vision system that detects and tracks seat occupancy in cinema CCTV footage using YOLOv8 for person detection and Supervision for zone-based seat occupancy analysis.
Example output showing:
- π₯ Red seats β Occupied
- π© Green seats β Empty
- πͺ Seat labels β A1, A2, B3, etc.
YOLOv8 model detects people in each video frame.
Pre-defined seat bounding boxes (from YOLO-format label files) are loaded as detection zones.
IoU (Intersection over Union) analysis determines if a detected person occupies a seat zone.
Moving window smoothing reduces false detections and flickering.
EasyOCR extracts CCTV timestamp overlay with smart fallback for missing frames.
Generates annotated video, JSON timeline, and CSV reports.
-
π― Accurate Person Detection
YOLOv8 model optimized for human detection. -
πͺ Zone-based Seat Mapping
Pre-defined seat bounding boxes as detection zones. -
π IoU-based Occupancy Logic
Determines seat occupation using intersection over union analysis. -
β±οΈ Temporal Smoothing
15-frame window with "Empty after 5 consecutive empty frames" logic. -
π OCR Timestamp Extraction
- Extracts date, time, and day from CCTV overlay
- Smart fallback for missing OCR detections
- Time interpolation between frames
-
π Multiple Output Formats
- Annotated video with colored seat overlays (green = empty, red = occupied)
- JSON timeline with complete detection history
- CSV exports at configurable intervals (1s and 60s)
-
π« Business Logic Integration
Example implementation for ticket purchase tracking.
git clone https://github.com/IamWei18/Cinema-Seat-Occupancy.git
cd cinema-seat-detectionpip install -r requirements.txtCreate a requirements.txt file:
ultralytics>=8.0.0
supervision>=0.18.0
opencv-python>=4.8.0
shapely>=2.0.0
tqdm>=4.66.0
easyocr>=1.7.0
pandas>=2.0.0
numpy>=1.24.0
- CCTV footage in MP4 format
- Recommended resolution: 1920Γ1080
Seat bounding boxes must follow the YOLO annotation format:
class x_center y_center width height
Example seat label file:
0 0.581323 0.057947 0.023623 0.097636
0 0.553012 0.059395 0.026759 0.098440
0 0.445808 0.071541 0.034594 0.087194
0 0.481932 0.065562 0.031584 0.085689
0 0.516902 0.061507 0.029797 0.088030
0 0.224566 0.587128 0.032454 0.131741
0 0.254181 0.587609 0.024611 0.130779
- Pre-trained YOLOv8 model for person detection.
- Custom-trained models can be used for improved accuracy.
Edit config.py to customize parameters.
# config.py
from pathlib import Path
# File paths
VIDEO_PATH = "/path/to/your/video.mp4"
SEAT_LABEL_PATH = "/path/to/seat_labels.txt"
YOLO_MODEL_PATH = "/path/to/yolov8_weights.pt"
OUTPUT_DIR = Path("/path/to/output/directory")
# Model settings
CONFIDENCE_THRESHOLD = 0.1
IOU_THRESHOLD = 0.45
# Processing settings
FRAME_STRIDE = 5
SMOOTH_WINDOW = 15
OCCUPANCY_THRESHOLDS = {
"iop": 0.15, # Intersection over polygon
"iob": 0.5 # Intersection over box
}
# CSV export intervals (seconds)
CSV_INTERVALS = [1, 60]
# Seat layout (optional)
ROW_COUNTS = [9, 8, 8, 8, 8]
ROW_LABELS = ["E", "D", "C", "B", "A"]
# Timestamp OCR region
TIME_REGION = (246, 915, 852, 70) # x, y, w, hfrom video_processor import process_video
from config import *
process_video(
video_path=VIDEO_PATH,
seat_label_path=SEAT_LABEL_PATH,
model_path=YOLO_MODEL_PATH,
output_dir=OUTPUT_DIR
)# main.py
from video_processor import CinemaSeatDetector
from config import *
def main():
detector = CinemaSeatDetector(
video_path=VIDEO_PATH,
seat_label_path=SEAT_LABEL_PATH,
model_path=YOLO_MODEL_PATH,
output_dir=OUTPUT_DIR
)
detector.run()
if __name__ == "__main__":
main()The system generates three types of outputs.
Original video with seat boundaries drawn.
- π© Green boundaries β Empty seats
- π₯ Red boundaries β Occupied seats
- Seat labels displayed above each seat
Example:
[
{
"timeline": "11-12-2025 Wed 21:16:30",
"second": 0,
"date": "11-12-2025",
"time": "21:16:30",
"seats": {
"A1": "Empty",
"A2": "Occupied",
"A3": "Empty"
}
}
]| date | time | seat | status | ticket_purchase | compliance | alert | ticket_number | sms_to_worker |
|---|---|---|---|---|---|---|---|---|
| 11/6/2025 | 21:16:30 | A1 | Empty | no | no | 0 | #A00001 | no |
| 11/6/2025 | 21:16:30 | A2 | Occupied | yes | yes | 0 | #A00002 | no |
Same format but sampled every 60 seconds.
cinema-seat-detection/
β
βββ config.py
βββ seat_utils.py
βββ seat_tracker.py
βββ timestamp_extractor.py
βββ csv_writer.py
βββ video_processor.py
βββ main.py
βββ requirements.txt
βββ README.md
| File | Description |
|---|---|
config.py |
Configuration settings |
seat_utils.py |
Seat loading and naming utilities |
seat_tracker.py |
Temporal smoothing tracker |
timestamp_extractor.py |
OCR timestamp extraction |
csv_writer.py |
CSV generation utilities |
video_processor.py |
Main processing pipeline |
main.py |
Entry point |
-
load_yolo_bboxes()
Loads seat bounding boxes from YOLO label files. -
group_and_name_seats()
Groups seats into rows and assigns labels (A1, A2, ...).
SmoothStatusTracker
Maintains history for each seat and applies smoothing logic.
extract_time_from_frame()
Functions:
- Extracts date and time using EasyOCR
- Uses regex for date
(DD-MM-YYYY)and time(HH:MM:SS) - Performs time interpolation for missing OCR frames
Main class:
CinemaSeatDetector
Responsibilities:
- Frame-by-frame video processing
- IoU-based seat occupancy detection
- Generating all output formats
Modify config.py.
ROW_COUNTS = [9, 8, 8, 8, 8]
ROW_LABELS = ["E", "D", "C", "B", "A"]Adjust IoU thresholds.
OCCUPANCY_THRESHOLDS = {
"iop": 0.15,
"iob": 0.5
}Lower values = more sensitive detection.
CSV_INTERVALS = [1, 30, 60]Exports reports every 1s, 30s, and 60s.
Specify your license here.
Example:
MIT License
- YOLOv8 by Ultralytics
- Supervision Library by Roboflow
- EasyOCR by Jaided AI
