Неделя 14 - Финальный проект
1. Обзор проекта: Система мониторинга парковки
Разработаем комплексную систему для:
Детекции свободных/занятых парковочных мест
Распознавания номеров автомобилей
Визуализации статистики
Интеграции с базой данных
2. Архитектура решения
2.1. Компоненты системы
1. Модуль захвата видео (камеры/файлы)
2. Детектор парковочных мест (YOLOv5)
3. OCR для номеров (EasyOCR/Tesseract)
4. База данных (SQLite/PostgreSQL)
5. Веб-интерфейс (Flask/Dash)
2.2. Технологический стек
mermaid
Copy
graph TD
A[Камеры] --> B(OpenCV Video Capture)
B --> C[Детекция автомобилей]
C --> D[Распознавание номеров]
D --> E[Анализ занятости]
E --> F[(База данных)]
F --> G[Веб-интерфейс]
3. Реализация основных модулей
3.1. Детекция парковочных мест
python
Copy
import cv2
import numpy as np
class ParkingSpaceDetector:
def __init__(self, model_path='parking_detector.onnx'):
self.net = cv2.dnn.readNet(model_path)
self.classes = ['free', 'occupied']
def detect(self, img, conf_threshold=0.5):
blob = cv2.dnn.blobFromImage(img, 1/255.0, (640,640), swapRB=True)
self.net.setInput(blob)
outputs = self.net.forward()
results = []
h, w = img.shape[:2]
for detection in outputs[0][0]:
scores = detection[5:]
class_id = np.argmax(scores)
confidence = scores[class_id]
if confidence > conf_threshold:
box = detection[0:4] * np.array([w, h, w, h])
(centerX, centerY, width, height) = box.astype("int")
x = int(centerX - (width / 2))
y = int(centerY - (height / 2))
results.append({
'class': self.classes[class_id],
'confidence': float(confidence),
'box': (x, y, int(width), int(height))
})
return results
3.2. Распознавание номеров
python
Copy
import easyocr
class LicensePlateRecognizer:
def __init__(self):
self.reader = easyocr.Reader(['en', 'ru'])
def recognize(self, img):
# Предварительная обработка
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (3,3), 0)
thresh = cv2.adaptiveThreshold(blurred, 255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, 11, 2)
# Распознавание
results = self.reader.readtext(thresh)
if results:
return results[0][1] # Возвращаем самый достоверный результат
return None
3.3. Интеграция с базой данных
python
Copy
import sqlite3
from datetime import datetime
class ParkingDatabase:
def __init__(self, db_path='parking.db'):
self.conn = sqlite3.connect(db_path)
self._create_tables()
def _create_tables(self):
cursor = self.conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS parking_spaces (
id INTEGER PRIMARY KEY,
status TEXT,
plate_number TEXT,
timestamp DATETIME
)
''')
self.conn.commit()
def update_space(self, space_id, status, plate_number=None):
cursor = self.conn.cursor()
cursor.execute('''
INSERT INTO parking_spaces (id, status, plate_number, timestamp)
VALUES (?, ?, ?, ?)
''', (space_id, status, plate_number, datetime.now()))
self.conn.commit()
def get_occupancy_stats(self):
cursor = self.conn.cursor()
cursor.execute('''
SELECT status, COUNT(*)
FROM parking_spaces
GROUP BY status
''')
return dict(cursor.fetchall())
4. Основной цикл обработки
python
Copy
def main_processing_loop(video_source=0):
# Инициализация компонентов
detector = ParkingSpaceDetector()
recognizer = LicensePlateRecognizer()
db = ParkingDatabase()
cap = cv2.VideoCapture(video_source)
while True:
ret, frame = cap.read()
if not ret:
break
# Детекция парковочных мест
spaces = detector.detect(frame)
for space in spaces:
x,y,w,h = space['box']
status = space['class']
# Выделение области номера (нижняя часть bounding box)
plate_roi = frame[y+h//2:y+h, x:x+w]
# Распознавание номера для занятых мест
plate_number = None
if status == 'occupied':
plate_number = recognizer.recognize(plate_roi)
# Визуализация
color = (0,255,0) if status == 'free' else (0,0,255)
cv2.rectangle(frame, (x,y), (x+w,y+h), color, 2)
if plate_number:
cv2.putText(frame, plate_number, (x,y-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255,255,255), 2)
# Обновление базы данных
space_id = f"{x}_{y}_{w}_{h}" # Уникальный ID места
db.update_space(space_id, status, plate_number)
# Отображение статистики
stats = db.get_occupancy_stats()
free = stats.get('free', 0)
occupied = stats.get('occupied', 0)
total = free + occupied
cv2.putText(frame, f"Free: {free} ({free/total:.0%})", (20,40),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0,255,0), 2)
cv2.putText(frame, f"Occupied: {occupied} ({occupied/total:.0%})", (20,80),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255), 2)
cv2.imshow('Parking Monitoring', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
5. Визуализация данных
5.1. Веб-интерфейс на Flask
python
Copy
from flask import Flask, render_template
import sqlite3
from datetime import datetime, timedelta
app = Flask(__name__)
@app.route('/')
def dashboard():
conn = sqlite3.connect('parking.db')
cursor = conn.cursor()
# Текущая статистика
cursor.execute('''
SELECT status, COUNT(*)
FROM (
SELECT id, status
FROM parking_spaces
GROUP BY id
HAVING MAX(timestamp)
)
GROUP BY status
''')
current_stats = dict(cursor.fetchall())
# Исторические данные
history = []
for i in range(7):
day = datetime.now() - timedelta(days=i)
cursor.execute('''
SELECT status, COUNT(*)
FROM parking_spaces
WHERE date(timestamp) = date(?)
GROUP BY status
''', (day,))
history.append({
'date': day.strftime('%Y-%m-%d'),
'stats': dict(cursor.fetchall())
})
conn.close()
return render_template('dashboard.html',
current=current_stats,
history=reversed(history))
if __name__ == '__main__':
app.run(debug=True)
5.2. HTML шаблон (dashboard.html)
html
Copy
<!DOCTYPE html>
<html>
<head>
<title>Parking Monitoring</title>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
</head>
<body>
<h1>Parking Occupancy</h1>
<div id="current" style="width:600px;height:300px;"></div>
<h2>Last 7 Days</h2>
<div id="history" style="width:800px;height:400px;"></div>
<script>
// Текущая статистика
var free = {{ current.get('free', 0) }};
var occupied = {{ current.get('occupied', 0) }};
var data = [{
values: [free, occupied],
labels: ['Free', 'Occupied'],
type: 'pie'
}];
Plotly.newPlot('current', data);
// Исторические данные
var historyDates = {{ [day['date'] for day in history]|tojson }};
var freeHistory = {{ [day['stats'].get('free', 0) for day in history]|tojson }};
var occupiedHistory = {{ [day['stats'].get('occupied', 0) for day in history]|tojson }};
var historyData = [
{
x: historyDates,
y: freeHistory,
name: 'Free',
type: 'bar'
},
{
x: historyDates,
y: occupiedHistory,
name: 'Occupied',
type: 'bar'
}
];
Plotly.newPlot('history', historyData, {barmode: 'stack'});
</script>
</body>
</html>
Run HTML
6. Дополнительные улучшения
6.1. Масштабирование с Redis
python
Copy
import redis
import json
class RedisParkingDatabase:
def __init__(self, host='localhost', port=6379):
self.r = redis.Redis(host=host, port=port)
def update_space(self, space_id, status, plate_number=None):
data = {
'status': status,
'plate_number': plate_number,
'timestamp': datetime.now().isoformat()
}
self.r.hset('parking_spaces', space_id, json.dumps(data))
def get_occupancy_stats(self):
spaces = self.r.hgetall('parking_spaces')
stats = {'free': 0, 'occupied': 0}
for space_id, space_data in spaces.items():
data = json.loads(space_data)
stats[data['status']] += 1
return stats
6.2. Уведомления с Telegram Bot
python
Copy
import requests
class TelegramNotifier:
def __init__(self, token, chat_id):
self.token = token
self.chat_id = chat_id
self.base_url = f"https://api.telegram.org/bot{token}"
def send_notification(self, message):
url = f"{self.base_url}/sendMessage"
params = {
'chat_id': self.chat_id,
'text': message
}
requests.post(url, params=params)
# Использование
notifier = TelegramNotifier('YOUR_TOKEN', 'CHAT_ID')
notifier.send_notification("Parking is 90% full!")
7. Практическое задание
1. Реализуйте систему мониторинга парковки с базовым функционалом
2. Добавьте распознавание номеров автомобилей
3. Создайте веб-интерфейс для визуализации данных
4. (Дополнительно) Реализуйте уведомления о заполнении парковки
5. Протестируйте систему на реальных/симулированных данных
8. Итоги
Разработана комплексная система мониторинга парковки
Использованы современные методы компьютерного зрения
Реализована интеграция с базой данных
Создан веб-интерфейс для визуализации
Предложены пути масштабирования и улучшения
Заключение: Поздравляем с завершением курса! Теперь вы готовы создавать собственные системы компьютерного зрения.