본문 바로가기
개발 언어/Python

[python] 블랙박스 영상, 자동차 번호판 찾아봐?

by 주호파파 2025. 4. 29.
728x90
반응형

블랙박스에서 자동차 번호를 추출해서 엑셀 csv로 변환해주는 테스트 웹사이트을 구축해보았습니다.

프로그램 코드

main.py

 
# main.py

import os
from flask import Flask, request, redirect, url_for, render_template, send_from_directory
from werkzeug.utils import secure_filename
import cv2
import easyocr
import pandas as pd
import datetime
import re
import logging

# 로깅 설정 추가 (맨 위쪽에 추가)
logging.basicConfig(level=logging.INFO,
                   format='%(asctime)s - %(levelname)s - %(message)s')

# 허용된 파일 확장자 설정
ALLOWED_EXTENSIONS = {'mp4', 'avi', 'mov', 'mkv'}

# 업로드 폴더 설정
UPLOAD_FOLDER = 'uploads'
RESULT_FOLDER = 'results'
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
os.makedirs(RESULT_FOLDER, exist_ok=True)

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

# 허용된 파일인지 확인하는 함수
def allowed_file(filename):
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

# 한국 번호판 패턴 검증 함수 추가
def validate_korean_plate(text):
    # 문자열에서 공백 제거
    text = re.sub(r'\s+', '', text)
    
    # 숫자 2-3개 + 한글 1개 + 숫자 4개 패턴
    pattern1 = r'\d{2,3}[가-힣]\d{4}'
    # 한글 2개 + 숫자 4개 패턴 (외교차량, 국제기구 등)
    pattern2 = r'[가-힣]{2}\d{4}'
    
    # 텍스트 내에서 패턴 찾기
    matches = re.findall(pattern1, text) or re.findall(pattern2, text)
    if matches:
        return matches[0]
    return None

# 비디오 처리 함수
def process_video(filepath):
    # 한국어 인식을 위해 'ko' 추가
    reader = easyocr.Reader(['ko', 'en'], gpu=True)  # GPU 사용 설정
    cap = cv2.VideoCapture(filepath)
    
    detected_plates = []
    frame_rate = cap.get(cv2.CAP_PROP_FPS)
    total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
    
    logging.info(f"비디오 처리 시작: {filepath}")
    logging.info(f"총 프레임 수: {total_frames}, 프레임레이트: {frame_rate}")
    
    current_frame = 0
    while cap.isOpened():
        ret, frame = cap.read()
        if not ret:
            break
        
        # 3프레임당 한 번씩만 처리 (기존 5프레임에서 3프레임으로 수정)
        if current_frame % 3 != 0:
            current_frame += 1
            continue
        
        # 프로그레스 로깅
        if current_frame % 30 == 0:
            progress = (current_frame / total_frames) * 100
            logging.info(f"처리 진행률: {progress:.1f}%")
            
        current_time = current_frame / frame_rate
        
        # 이미지 전처리
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        blurred = cv2.GaussianBlur(gray, (5, 5), 0)
        
        # OCR 처리
        results = reader.readtext(blurred)
        for (bbox, text, prob) in results:
            # 확률 75% 이상으로 낮춤 (기존 85%에서 조정)
            if prob > 0.75:
                valid_plate = validate_korean_plate(text)
                if valid_plate:
                    plate_info = {
                        "time": str(datetime.timedelta(seconds=current_time)),
                        "plate": valid_plate,
                        "confidence": round(prob * 100, 2)
                    }
                    detected_plates.append(plate_info)
                    logging.info(f"번호판 감지: {plate_info}")
    
        current_frame += 1
    
    cap.release()
    
    logging.info(f"비디오 처리 완료. 감지된 번호판 수: {len(detected_plates)}")
    return detected_plates

# 업로드 페이지 라우트
@app.route('/', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        if 'file' not in request.files:
            return '파일이 없어요!'
        file = request.files['file']
        if file.filename == '':
            return '선택된 파일이 없어요!'
        if file and allowed_file(file.filename):
            filename = secure_filename(file.filename)
            filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
            file.save(filepath)
            
            logging.info(f"파일 업로드 완료: {filename}")
            
            # 비디오 처리
            detected_plates = process_video(filepath)
            
            if not detected_plates:
                logging.warning("감지된 번호판이 없습니다!")
                return "번호판을 감지하지 못했습니다. 다른 영상을 시도해보세요."
            
            # 결과를 CSV로 저장
            df = pd.DataFrame(detected_plates)
            csv_filename = f"{filename.rsplit('.',1)[0]}_license_plates.csv"
            csv_path = os.path.join(RESULT_FOLDER, csv_filename)
            df.to_csv(csv_path, index=False, encoding='utf-8-sig')
            
            logging.info(f"결과 저장 완료: {csv_filename}")
            return redirect(url_for('download_file', filename=csv_filename))
    return '''
    <!doctype html>
    <title>블랙박스 동영상 업로드</title>
    <h1>블랙박스 동영상을 업로드하세요</h1>
    <form method=post enctype=multipart/form-data>
      <input type=file name=file accept="video/*">
      <input type=submit value=업로드>
    </form>
    '''

# 결과 파일 다운로드 라우트
@app.route('/download/<filename>')
def download_file(filename):
    return send_from_directory(RESULT_FOLDER, filename, as_attachment=True)

if __name__ == '__main__':
    app.run(debug=True)

프로그램 동작 설명

1. 필요한 라이브러리 임포트: Flask 웹 프레임워크, OpenCV, EasyOCR, Pandas 등을 불러옵니다.

2. 파일 업로드 설정:

허용된 비디오 파일 확장자를 설정하고, 업로드 및 결과 저장 폴더를 생성합니다.

3. Flask 애플리케이션 설정:

Flask 앱을 초기화하고, 업로드 폴더를 설정합니다.

4. 파일 확장자 검증 함수:

업로드된 파일의 확장자가 허용된 형식인지 확인하는 함수를 정의합니다.

5. 비디오 처리 함수 (process_video):

비디오 파일을 프레임 단위로 읽어들이고, EasyOCR을 사용하여 각 프레임에서 번호판을 인식합니다.

인식된 번호판과 해당 프레임의 타임스탬프를 리스트에 저장합니다.

 

6. 루트 라우트 (/):

GET 요청 시 업로드 폼을 보여주고, POST 요청 시 파일을 업로드합니다.

업로드된 파일이 유효하면 저장 후 process_video 함수를 호출하여 번호판을 인식하고, 결과를 CSV 파일로 저장합니다.

CSV 파일 다운로드 페이지로 리디렉션합니다.

 

7. 다운로드 라우트 (/download/<filename>):

처리된 CSV 파일을 사용자에게 다운로드할 수 있도록 제공합니다.

 

8. 애플리케이션 실행:

D:\study\python>python main.py

웹페이지 접속 ( http://127.0.0.1:5000/ )

영상 업로드 하면... 자동으로 분석해서 검색된 자동차 번호판을 csv로 변환해줍니다.

728x90
반응형