24년 ICT멘토링 X 해상물류 기업연계형 프로젝트 입선작
main.py
import sqlite3
from flask import Flask, Response, render_template, jsonify, send_file, send_from_directory
import cv2
import time
from ultralytics import YOLO
import socket
import os
import sys
from datetime import datetime, timedelta
from PyKakao import Message
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.lib.pagesizes import A4
from reportlab.platypus import Paragraph
from reportlab.lib.styles import ParagraphStyle
from reportlab.platypus import BaseDocTemplate, PageTemplate, Frame
from reportlab.lib.units import cm
from reportlab.platypus.flowables import HRFlowable
from reportlab.pdfgen import canvas
from PyPDF2 import PdfMerger
import shutil
import matplotlib.pyplot as plt
app = Flask(__name__)
# --------------------- PDF 설정 ---------------------------#
pdfmetrics.registerFont(TTFont("맑은고딕", "malgun.ttf"))
text_frame = Frame(
x1=2.54 * cm,
y1=2.54 * cm,
height=24.16 * cm,
width=15.92 * cm,
leftPadding=0 * cm,
bottomPadding=0 * cm,
rightPadding=0 * cm,
topPadding=0 * cm,
showBoundary=0,
id='text_frame'
)
# --------------------- SQLite 설정 ---------------------------#
def init_db():
try:
# 기존 데이터베이스 파일 삭제
if os.path.exists('logs.db'):
os.remove('logs.db')
print("Existing database file removed.")
# 새 데이터베이스 파일 생성
conn = sqlite3.connect('logs.db')
c = conn.cursor()
# 로그 저장용 테이블 생성
c.execute('''CREATE TABLE IF NOT EXISTS logs (id INTEGER PRIMARY KEY AUTOINCREMENT, message TEXT, timestamp TEXT)''')
conn.commit()
conn.close()
print("Database initialized and table created successfully.")
except Exception as e:
print(f"Error initializing database: {e}")
def add_log_to_db(log):
try:
conn = sqlite3.connect('logs.db')
c = conn.cursor()
c.execute("INSERT INTO logs (message, timestamp) VALUES (?, ?)", (log, time.strftime('%Y-%m-%d %H:%M:%S')))
conn.commit()
except Exception as e:
print(f"Error adding log to database: {e}")
finally:
conn.close()
# 데이터베이스에서 하루 동안의 로그를 가져오는 함수
def get_logs_from_db(start_time, end_time, limit=100, offset=0):
with sqlite3.connect('logs.db') as conn:
cursor = conn.cursor()
query = """
SELECT id, message, timestamp
FROM logs
WHERE timestamp BETWEEN ? AND ?
LIMIT ? OFFSET ?
"""
cursor.execute(query, (start_time, end_time, limit, offset))
return cursor.fetchall()
def delete_logs_from_db(log_ids):
conn = sqlite3.connect('logs.db')
c = conn.cursor()
c.execute("DELETE FROM logs WHERE id IN ({})".format(','.join('?' * len(log_ids))), log_ids)
conn.commit()
conn.close()
# DB에서 [[몇시,로그몇개],[몇시,로그몇개]] 이런식으로 리턴해주는 함수.
#select 부분 -> hour , count 선언 및 초기화
#시간대별로 묶고 정렬함. count의 조건이 where임
def get_hourly_log_counts(start_time, end_time):
with sqlite3.connect('logs.db') as conn:
cursor = conn.cursor()
query = """
SELECT strftime('%H', timestamp) as hour, COUNT(*) as count
FROM logs
WHERE timestamp BETWEEN ? AND ?
GROUP BY hour
ORDER BY hour
"""
cursor.execute(query, (start_time, end_time))
return cursor.fetchall()
#Maplolib 이용해서 그래프 그리는 함수.
def create_hourly_log_graph(start_time, end_time):
threshold = 50
hourly_data = get_hourly_log_counts(start_time, end_time)
# 데이터 분리
hours = [int(row[0]) for row in hourly_data]
counts = [row[1] for row in hourly_data]
# 그래프 그리기
plt.figure(figsize=(10, 6))
colors = ['red' if count > threshold else 'skyblue' for count in counts] # threshold를 설정하여 기준 초과 시 색상 변경
plt.bar(hours, counts, color=colors, edgecolor='black')
plt.xlabel('Hour of the Day')
plt.ylabel('Log Count')
plt.title('Hourly Log Count')
plt.xticks(range(24)) # X축에 0-23까지 시간 표시, x축 구간별로 짧은 세로선 찍어주는거
# y축 범위를 동적으로 설정
if counts: # counts 리스트가 비어있지 않을 경우
max_count = max(counts)
plt.ylim(0, max_count * 1.1) #y축 값을 0부터 최댓값의 10% 여유를 두고 설정
plt.grid(True, axis='y', linestyle='--', alpha=0.7) #그리드 -> 격자무늬 그리기
# 그래프를 파일로 저장
graph_path = 'static/hourly_log_graph.png' #웹페이지에 전달하기위해 static 폴더에 저장
plt.savefig(graph_path)
plt.close()
return graph_path
# --------------------- 앱 로직 ---------------------------#
def detect_objects_and_send_alert():
#---------------------------------kakao-----------------------------------#
# API 인스턴스 생성
#API = Message(service_key="4becb5d2ab65d7bb5e2426d70e6af328") #이거 내껄로 바꾸기
# 인증 URL을 생성합니다.
#auth_url = API.get_url_for_generating_code()
#print("인증 URL:", auth_url)
# 리디렉션 URL에서 받은 코드를 사용하여 액세스 토큰을 가져옵니다.
#url = "https://localhost:5000/?code=JydZE6oP_KEIqV1F41bv2NqQk-tNQY7pbh3NyLwu_nsHTYWduTELJAAAAAQKPCJRAAABkn8RVYAq17LwdM8QAg"
#access_token = API.get_access_token_by_redirected_url(url)
#print("액세스 토큰:", access_token) # 액세스 토큰을 확인합니다.
#API.set_access_token(access_token)
#friend_uuids = ["h7OCtYC0jLmArJ6qk6efpp6vmraHtoe_irmB8Q",]
#---------------------------------kakao-----------------------------------#
cap = cv2.VideoCapture(0)
model = YOLO('green.pt', verbose=False)
#model = YOLO('test_140.pt', verbose=False)
last_helmet_alert_time = time.time()
last_mask_alert_time = time.time()
last_graph_drawing_time = time.time()
cooldown_period = 5 # 카톡 전송 쿨다운 시간 (초)
graph_drawing_cooldown = 5
# TCP 통신 설정
#server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#server_socket.bind(('172.18.168.222', 12346))
#server_socket.listen(1)
#print("Waiting for a connection...")
#client_socket, client_address = server_socket.accept()
#print(f"Connected to {client_address}")
while True:
success, frame = cap.read()
if not success:
break
results = model(frame)
img = results[0].plot()
boxes = results[0].boxes
class_ids = boxes.cls.int().tolist() if boxes.cls is not None else []
class_names = results[0].names
detected_classes = [class_names[class_id] for class_id in class_ids]
current_time = time.time()
flag = 0
if 'no_mask' in detected_classes:
flag = flag + 1
if (current_time - last_mask_alert_time) >= cooldown_period:
log_entry = "마스크 미착용이 감지되었습니다."
print("로그 추가전 확인용 출력")
add_log_to_db(log_entry)
'''
API.send_message_to_friend(
message_type="text",
receiver_uuids=friend_uuids,
text="마스크 미착용이 감지되었습니다.",
link={
"web_url": "https://developers.kakao.com",
"mobile_web_url": "https://developers.kakao.com"},
button_title="바로 확인",
)'''
print("마스크 미착용이 감지되었습니다. 카톡을 전송했습니다.")
last_mask_alert_time = current_time
if 'no_helmet' in detected_classes:
flag += 1
if (current_time - last_helmet_alert_time) >= cooldown_period:
log_entry = "헬멧 미착용이 감지되었습니다."
print("로그 추가전 확인용 출력")
add_log_to_db(log_entry)
'''
API.send_message_to_friend(
message_type="text",
receiver_uuids=friend_uuids,
text="헬멧 미착용이 감지되었습니다.",
link={
"web_url": "https://developers.kakao.com",
"mobile_web_url": "https://developers.kakao.com"},
button_title="바로 확인",
)'''
print("헬멧 미착용이 감지되었습니다. 카톡을 전송했습니다.")
last_helmet_alert_time = current_time
'''
if flag == 0:
message = '0'
client_socket.send(message.encode('utf-8'))
elif flag == 1:
message = '1'
client_socket.send(message.encode('utf-8'))
elif flag == 2:
message = '2'
client_socket.send(message.encode('utf-8'))
'''
# 데이터 전송
_, buffer = cv2.imencode('.jpg', img)
frame = buffer.tobytes()
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
cap.release()
@app.route('/video_feed')
def video_feed():
return Response(detect_objects_and_send_alert(), mimetype='multipart/x-mixed-replace; boundary=frame')
@app.route('/')
def index():
return render_template('index.html')
@app.route('/logs')
def get_logs():
now = datetime.now()
start_time = now - timedelta(seconds=3) # 최근 3초 동안의 로그를 조회
end_time = now
log_entries = get_logs_from_db(start_time.strftime('%Y-%m-%d %H:%M:%S'), end_time.strftime('%Y-%m-%d %H:%M:%S'))
return jsonify(log_entries)
@app.route('/logs/download')
def download_log():
pdf_dir = os.path.join(app.root_path, 'pdfs')
# 기존 PDF 디렉토리와 그 안의 파일들 삭제
if os.path.exists(pdf_dir):
shutil.rmtree(pdf_dir)
# 새 PDF 디렉토리 생성
os.makedirs(pdf_dir, exist_ok=True)
now = datetime.now()
start_time = datetime(now.year, now.month, now.day, 0, 0) # 09:00 시작 start_time = datetime(now.year, now.month, now.day, 9, 0)
end_time = datetime(now.year, now.month, now.day, 23, 59) # 18:00 종료 end_time = datetime(now.year, now.month, now.day, 18, 0)
# pdf_paths = []
pdf_path = os.path.join(pdf_dir, f'report_{now.strftime("%Y%m%d_%H%M")}.pdf')
c = canvas.Canvas(pdf_path, pagesize=A4)
width, height = A4
# 제목 및 가로선 추가
c.setFont("맑은고딕", 21)
c.drawString(2.54 * cm, height - 2.54 * cm, '모니터링 결과 로그')
c.setFont("맑은고딕", 12)
c.line(2.54 * cm, height - 2.54 * cm - 20, width - 2.54 * cm, height - 2.54 * cm - 20)
# 로그 데이터를 가져와서 PDF에 추가
offset = 0
limit = 100 # 한 번에 가져올 로그의 개수
y_position = height - 2.54 * cm - 40
while True:
logs_from_db = get_logs_from_db(
start_time.strftime('%Y-%m-%d %H:%M:%S'),
end_time.strftime('%Y-%m-%d %H:%M:%S'),
limit, offset
)
if not logs_from_db:
break
for log_id, message, timestamp in logs_from_db:
log_entry = f"{timestamp} - {message}"
if y_position < 2.54 * cm:
c.showPage()
c.setFont("맑은고딕", 12)
y_position = height - 2.54 * cm - 40
c.drawString(2.54 * cm, y_position, log_entry)
y_position -= 20
offset += limit
c.save()
# 생성된 PDF 파일을 클라이언트에게 제공
return send_file(pdf_path, as_attachment=True)
@app.route('/generate_graph')
def generate_graph():
# 시작 시간과 종료 시간을 정의합니다. 여기서는 0시부터 24시까지를 예시로 사용합니다.
start_time = 0
end_time = 24
# 그래프 생성 함수 호출
create_hourly_log_graph(start_time, end_time)
return '', 204 # 응답 본문 없음, 성공을 나타내는 204 No Content 반환
@app.route('/static/<path:filename>') #이 엔드포인트는 직접적으로 호출 되지는 않고, html에서 img 요청시 이게 작동.
def static_files(filename):
return send_from_directory('static', filename)
if __name__ == '__main__':
init_db() # 데이터베이스 초기화
app.run(host='0.0.0.0', port=12345, debug=True)
index.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>모니터링 시스템</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4; /* 배경색 추가 */
margin: 0;
padding: 20px;
}
.banner-container img {
width: 100%; /* 이미지를 페이지 전체 너비로 설정 */
display: block; /* 이미지 아래에 여백 제거 */
max-height: 60%;
border-radius: 10px; /* 이미지 둥근 모서리 */
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); /* 그림자 추가 */
}
.title-container {
text-align: center; /* 텍스트를 중앙 정렬 */
margin: 20px 0; /* 상하단에 여백 추가 */
padding: 20px;
background-color: #4CAF50; /* 배경색 추가 */
color: white; /* 글자 색상 변경 */
border-radius: 8px; /* 둥근 모서리 */
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); /* 그림자 추가 */
font-size: 24px; /* 글자 크기 변경 */
}
.container {
display: flex;
justify-content: space-between;
margin-top: 20px;
}
video {
max-width: 70%;
height: auto;
border-radius: 10px; /* 비디오 둥근 모서리 */
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); /* 그림자 추가 */
}
.log-container {
display: flex;
flex-direction: column;
justify-content: space-between;
max-width: 28%;
padding: 10px;
border: 1px solid #ccc;
background-color: #f9f9f9;
border-radius: 8px; /* 둥근 모서리 */
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); /* 그림자 추가 */
}
.log {
max-height: 400px;
overflow-y: auto;
flex-grow: 1;
margin-bottom: 10px;
}
button {
padding: 10px 20px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 5px; /* 둥근 모서리 */
cursor: pointer;
align-self: flex-end;
transition: background-color 0.3s; /* 부드러운 배경색 변화 */
}
button:hover {
background-color: #45a049;
}
#graphPopup {
display: none;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: white;
padding: 20px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.5);
border-radius: 10px; /* 둥근 모서리 */
}
</style>
</head>
<body>
<div class="banner-container">
<img src="{{ url_for('static', filename='images/jjin.png') }}" alt="배너 이미지">
</div>
<div class="title-container">
<h1>모니터링 시스템</h1>
</div>
<div class="container" style="margin-bottom: 10px">
<div>
<img src="/video_feed" width="640" height="480">
</div>
<div class="log-container">
<div class="log" id="log">
<!-- 로그 내용이 여기 표시됩니다 -->
</div>
<div style="display: flex; justify-content: space-between;">
<button onclick="showGraph()">그래프 보기</button> <!-- 그래프 보기 버튼 이동 -->
<button onclick="downloadLog()">로그 다운로드</button>
</div>
</div>
</div>
<div id="graphPopup">
<h2>시간대별 보호구 착용 상태 모니터링</h2>
<img id="graphImage" src="" alt="Hourly Log Graph" style="max-width: 100%;">
<button onclick="closeGraph()">닫기</button>
</div>
<script>
async function showGraph() {
// 그래프를 생성하는 서버 엔드포인트 호출
await fetch('/generate_graph');
// 그래프 이미지 URL 업데이트
const graphImage = document.getElementById('graphImage');
graphImage.src = "{{ url_for('static', filename='hourly_log_graph.png') }}" + "?" + new Date().getTime(); // 캐시 방지
document.getElementById('graphPopup').style.display = 'block';
}
function closeGraph() {
document.getElementById('graphPopup').style.display = 'none';
}
async function fetchLogs() {
const response = await fetch('/logs');
const logs = await response.json();
const logContainer = document.getElementById('log');
// 새로운 로그를 기존 로그에 누적
logs.forEach(log => {
logContainer.insertAdjacentHTML('beforeend', `<p>${log[2]} - ${log[1]}</p>`);
});
// 오래된 로그가 100개 이상이면 가장 오래된 로그 제거
const logEntries = logContainer.querySelectorAll('p');
if (logEntries.length > 100) {
logEntries[0].remove(); // 가장 오래된 로그 제거
}
// 스크롤을 가장 아래로 내리기
logContainer.scrollTop = logContainer.scrollHeight;
}
function downloadLog() {
window.location.href = '/logs/download'; // 다운로드를 위해 새로운 엔드포인트를 호출
}
// 5초마다 로그를 업데이트합니다.
setInterval(fetchLogs, 3000);
// 페이지 로드 시 첫 로그를 가져옵니다.
fetchLogs();
</script>
</body>
</html>
main.py -> YOLO8 기반 객체인식 + 특정 객체 검출시 카톡 + 특정 객체 검출시 웹사이트에 로그 남기기 + FLASK를 이용한 모니터링 웹사이트 + TCP통신을 통한 임베디드 시스템 제어
index.html -> 웹 CSS , Javascript, html 등등 + 로그 pdf다운로드 기능
리스트에 저장하는것에서 DB (sqlite3) 사용하여 로그 저장하는 방식.
그래프 그려주는 기능 추가 (웹사이트 팝업으로 볼 수 있음.)