흉기 난동 상황 탐지 서비스를 위한 객체 탐지 모델을 선정하는 과정에서 다양한 모델들을 검토하게 되었다. 보통은 이런 서비스를 개발할 때에 흔히 YOLO모델을 많이 사용하던데, 이 지점에서 의문이 생겼다. 이런 지도 학습 모델을 사용하려면 대량의 데이터가 필요할텐데 우리가 과연 그걸 해낼 수 있을까 → 졸업 프로젝트 단에서 이런 방식으로 프로덕트를 개발하기엔 공수가 너무 클 것이라고 느꼈고, 우리는 비지도학습 기반 객체 탐지 모델들 중 어떤 것을 채택할 지를 고민하였다. 후보군은 아래와 같았다.
Few-shot Learning 모델
적은 양의 학습 데이터로 모델을 학습시키는 방법이다. 기존의 학습된 모델을 기반으로 적은 양의 새로운 데이터를 통해 빠르게 학습할 수 있다
Zero-shot Learning 모델
학습된 객체 외의 새로운 객체를 인식할 수 있는 방법이다. 모델은 학습 데이터 외에도 객체의 속성이나 설명을 통해 새로운 객체를 인식한다. 이러한 방식은 추가적인 데이터 없이도 새로운 객체를 인식할 수 있어 매우 유연하다.
기본적으로 제로샷 러닝 모델들이 흥미롭다고 느껴졌고, 최소의 데이터도 필요하지 않다는 것이 우리 상황에서 매력적이라고 느껴 제로샷 러닝 모델을 사용해보고자 하였다.
Grounding DINO 모델을 선정한 이유는 다음과 같다
다른 제로샷 모델들과 비교했던 지점들이 분명 있었다.
그렇게 우리가 선정한 Grounding Dino 모델에도 몇 가지 종류가 있다.
우리는 연산 비용 대비 성능이 뛰어나다고 평가되는 Grounding DINO-T를 사용하기로 결정하였다.
먼저 필요한 라이브러리와 패키지를 설치한다.
pip install torch torchvision fastapi uvicorn opencv-python transformers
모델을 로드하고 필요한 설정을 한다.
import torch
from transformers import AutoTokenizer, AutoModelForObjectDetection
# 모델과 토크나이저 로드
tokenizer = AutoTokenizer.from_pretrained("GroundingDINO-T")
model = AutoModelForObjectDetection.from_pretrained("GroundingDINO-T")
# 디바이스 설정
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
총기, 칼, 도끼, 낫, 유리병, 돌, 몽둥이, 공구를 든 사람과 그 주변의 쓰러진 사람을 감지하는 객체 탐지 함수를 구현한다.
import cv2
import numpy as np
def detect_objects(frame, model, tokenizer, device):
# 이미지 전처리
inputs = tokenizer(images=frame, return_tensors="pt").to(device)
outputs = model(**inputs)
# 결과 처리
boxes = outputs.logits.argmax(-1)
scores = outputs.logits.max(-1).values
labels = outputs.labels
relevant_labels = ["gun", "knife", "axe", "sickle", "glass_bottle", "stone", "club", "tool", "fallen_person"]
results = []
for box, score, label in zip(boxes, scores, labels):
if score > 0.5 and label in relevant_labels:
results.append({
"box": box.cpu().numpy(),
"score": score.cpu().numpy(),
"label": label.cpu().numpy()
})
return results
결국 모델의 결과 값을 api 인터페이스를 통해 통신해야 하므로, FastAPI를 사용하여 객체 탐지 결과를 보내는 api 서버를 구축하였다.
from fastapi import FastAPI, UploadFile, File
from fastapi.responses import StreamingResponse
import io
app = FastAPI()
@app.post("/detect/")
async def detect(file: UploadFile = File(...)):
contents = await file.read()
np_img = np.frombuffer(contents, np.uint8)
img = cv2.imdecode(np_img, cv2.IMREAD_COLOR)
results = detect_objects(img, model, tokenizer, device)
for result in results:
box = result["box"]
label = result["label"]
cv2.rectangle(img, (box[0], box[1]), (box[2], box[3]), (0, 255, 0), 2)
cv2.putText(img, label, (box[0], box[1]-10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (36,255,12), 2)
_, img_encoded = cv2.imencode('.jpg', img)
return StreamingResponse(io.BytesIO(img_encoded.tobytes()), media_type="image/jpeg")
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
실시간으로 들어오는 영상에 대한 처리가 필요하기 때문에 socket을 활용하여 실시간 영상 처리를 구현하였다.
import socket
import threading
def video_streaming():
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('0.0.0.0', 8080))
server_socket.listen(1)
print('Listening for connection...')
connection, address = server_socket.accept()
print('Connected to:', address)
try:
while True:
data = connection.recv(1024)
if not data:
break
frame = np.frombuffer(data, dtype=np.uint8).reshape(480, 640, 3)
results = detect_objects(frame, model, tokenizer, device)
for result in results:
box = result["box"]
label = result["label"]
cv2.rectangle(frame, (box[0], box[1]), (box[2], box[3]), (0, 255, 0), 2)
cv2.putText(frame, label, (box[0], box[1]-10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (36,255,12), 2)
connection.sendall(frame.tobytes())
finally:
connection.close()
server_socket.close()
thread = threading.Thread(target=video_streaming)
thread.start()
흉기를 인식하는건 DINO-T 모델을 통해 알수있는 건가요?