๐Ÿ˜Aruco Marker

ArUco ๋งˆ์ปค ๊ฑฐ๋ฆฌ์ธก์ • ์‹ค์Šต ๊ฐ€์ด๋“œ

์ด ๊ฐ€์ด๋“œ๋Š” OpenCV๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ArUco ๋งˆ์ปค๋ฅผ ์ด์šฉํ•œ ๊ฑฐ๋ฆฌ ์ธก์ • ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋‹จ๊ณ„๋ณ„๋กœ ์•ˆ๋‚ดํ•ฉ๋‹ˆ๋‹ค.

๊ฐœ์š”

ArUco ๋งˆ์ปค๋Š” ์ฆ๊ฐ•ํ˜„์‹ค๊ณผ ์ปดํ“จํ„ฐ ๋น„์ „์—์„œ ๋„๋ฆฌ ์‚ฌ์šฉ๋˜๋Š” ์ •์‚ฌ๊ฐํ˜• ๋งˆ์ปค๋กœ, ๊ฐ ๋งˆ์ปค๋Š” ๊ณ ์œ ํ•œ ID๋ฅผ ๊ฐ€์ง€๋ฉฐ ์นด๋ฉ”๋ผ๋กœ ์‰ฝ๊ฒŒ ์ธ์‹ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์‹ค์Šต์—์„œ๋Š” ์นด๋ฉ”๋ผ ์ผˆ๋ฆฌ๋ธŒ๋ ˆ์ด์…˜๋ถ€ํ„ฐ ๊ฑฐ๋ฆฌ ์ธก์ •๊นŒ์ง€์˜ ์ „์ฒด ๊ณผ์ •์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

์‚ฌ์ „ ์ค€๋น„์‚ฌํ•ญ

ํ•„์š”ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜

pip install opencv-python==4.6.0.66
pip install opencv-contrib-python==4.6.0.66
pip install numpy

์ฒด์Šค๋ณด๋“œ ํŒจํ„ด ์ค€๋น„

  • 9x6 ๋‚ด๋ถ€ ์ฝ”๋„ˆ๋ฅผ ๊ฐ€์ง„ ์ฒด์Šค๋ณด๋“œ ํŒจํ„ด ์ธ์‡„ : https://calib.io/pages/camera-calibration-pattern-generator

  • ํ‰ํ‰ํ•œ ํ‘œ๋ฉด์— ๋ถ€์ฐฉ (์ค‘์š”: ํœ˜์–ด์ง€๋ฉด ์•ˆ๋จ)

  • ๊ถŒ์žฅ ํฌ๊ธฐ: A4 ์šฉ์ง€ ํฌ๊ธฐ

ArUco ๋งˆ์ปค ์ค€๋น„

  • 5x4 ArUco ๋งˆ์ปค ์ธ์‡„ (์˜จ๋ผ์ธ ์ƒ์„ฑ๊ธฐ ์‚ฌ์šฉ ๊ฐ€๋Šฅ)

  • ์ •ํ™•ํ•œ ํฌ๊ธฐ ์ธก์ • (์˜ˆ: 5cm x 5cm)


1๋‹จ๊ณ„: ์นด๋ฉ”๋ผ ์ผˆ๋ฆฌ๋ธŒ๋ ˆ์ด์…˜์šฉ ์‚ฌ์ง„ ์ดฌ์˜

์นด๋ฉ”๋ผ ์ผˆ๋ฆฌ๋ธŒ๋ ˆ์ด์…˜์„ ์œ„ํ•ด์„œ๋Š” ๋‹ค์–‘ํ•œ ๊ฐ๋„์™€ ์œ„์น˜์—์„œ ์ดฌ์˜ํ•œ ์ฒด์Šค๋ณด๋“œ ์ด๋ฏธ์ง€๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

์ดฌ์˜ ์Šคํฌ๋ฆฝํŠธ ์ž‘์„ฑ

import cv2
import os

# ์นด๋ฉ”๋ผ ์ดˆ๊ธฐํ™”
cap = cv2.VideoCapture(0)

# ์ด๋ฏธ์ง€ ์ €์žฅ ๊ฒฝ๋กœ ์„ค์ •
save_path = "calibration_images"
if not os.path.exists(save_path):
    os.makedirs(save_path)

image_count = 0

print("์ฒด์Šค๋ณด๋“œ๋ฅผ ๋‹ค์–‘ํ•œ ๊ฐ๋„๋กœ ์ดฌ์˜ํ•˜์„ธ์š”.")
print("'s' ํ‚ค๋ฅผ ๋ˆŒ๋Ÿฌ ์ €์žฅ, 'q' ํ‚ค๋ฅผ ๋ˆŒ๋Ÿฌ ์ข…๋ฃŒ")

while True:
    ret, frame = cap.read()
    if not ret:
        break
    
    # ํ™”๋ฉด์— ์ด๋ฏธ์ง€ ํ‘œ์‹œ
    cv2.imshow('Camera Calibration', frame)
    
    key = cv2.waitKey(1) & 0xFF
    
    # 's' ํ‚ค๋ฅผ ๋ˆ„๋ฅด๋ฉด ์ด๋ฏธ์ง€ ์ €์žฅ
    if key == ord('s'):
        filename = f"{save_path}/calib_{image_count:02d}.jpg"
        cv2.imwrite(filename, frame)
        print(f"์ด๋ฏธ์ง€ ์ €์žฅ๋จ: {filename}")
        image_count += 1
    
    # 'q' ํ‚ค๋ฅผ ๋ˆ„๋ฅด๋ฉด ์ข…๋ฃŒ
    elif key == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()
print(f"์ด {image_count}์žฅ์˜ ์ด๋ฏธ์ง€๊ฐ€ ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")

์ดฌ์˜ ๊ฐ€์ด๋“œ๋ผ์ธ

ํ•„์ˆ˜ ์กฐ๊ฑด

  • ์ตœ์†Œ 10-15์žฅ์˜ ์ด๋ฏธ์ง€ ํ•„์š” (20์žฅ ๊ถŒ์žฅ)

  • ๋‹ค์–‘ํ•œ ๊ฐ๋„์—์„œ ์ดฌ์˜ (0ยฐ, 15ยฐ, 30ยฐ, 45ยฐ ๋“ฑ)

  • ํ™”๋ฉด ์ „์ฒด์— ์ฒด์Šค๋ณด๋“œ๊ฐ€ ๋ณด์ด๋„๋ก ์ดฌ์˜

  • ํ™”๋ฉด ๋ชจ์„œ๋ฆฌ ๋ถ€๋ถ„๋„ ํฌํ•จํ•˜์—ฌ ์ดฌ์˜

ํ”ผํ•ด์•ผ ํ•  ๊ฒƒ

  • ํ๋ฆฟํ•˜๊ฑฐ๋‚˜ ์›€์ง์ž„์ด ์žˆ๋Š” ์‚ฌ์ง„

  • ์กฐ๋ช…์ด ๋„ˆ๋ฌด ์–ด๋‘ก๊ฑฐ๋‚˜ ๋ฐ์€ ์‚ฌ์ง„

  • ์ฒด์Šค๋ณด๋“œ๊ฐ€ ํœ˜์–ด์ง„ ์ƒํƒœ๋กœ ์ดฌ์˜๋œ ์‚ฌ์ง„


2๋‹จ๊ณ„: ์นด๋ฉ”๋ผ ์ผˆ๋ฆฌ๋ธŒ๋ ˆ์ด์…˜ ์ง„ํ–‰

์ดฌ์˜๋œ ์ด๋ฏธ์ง€๋“ค์„ ์‚ฌ์šฉํ•˜์—ฌ ์นด๋ฉ”๋ผ์˜ ๋‚ด๋ถ€ ๋งค๊ฐœ๋ณ€์ˆ˜์™€ ์™œ๊ณก ๊ณ„์ˆ˜๋ฅผ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.

์ผˆ๋ฆฌ๋ธŒ๋ ˆ์ด์…˜ ์Šคํฌ๋ฆฝํŠธ

import cv2
import numpy as np
import glob
import pickle

# ์ฒด์Šค๋ณด๋“œ ์„ค์ •
CHECKERBOARD = (9, 6)  # ๋‚ด๋ถ€ ์ฝ”๋„ˆ ์ˆ˜ (๊ฐ€๋กœ, ์„ธ๋กœ)
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)

# 3D ์  ์ค€๋น„
objp = np.zeros((CHECKERBOARD[0] * CHECKERBOARD[1], 3), np.float32)
objp[:, :2] = np.mgrid[0:CHECKERBOARD[0], 0:CHECKERBOARD[1]].T.reshape(-1, 2)

# ์ ๋“ค์„ ์ €์žฅํ•  ๋ฐฐ์—ด
objpoints = []  # 3D ์ 
imgpoints = []  # 2D ์ 

# ์ผˆ๋ฆฌ๋ธŒ๋ ˆ์ด์…˜ ์ด๋ฏธ์ง€ ๋กœ๋“œ
images = glob.glob('calibration_images/*.jpg')
print(f"์ด {len(images)}์žฅ์˜ ์ด๋ฏธ์ง€๋ฅผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.")

successful_images = 0
failed_images = []

for fname in images:
    img = cv2.imread(fname)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
    # ์ฒด์Šค๋ณด๋“œ ์ฝ”๋„ˆ ์ฐพ๊ธฐ
    ret, corners = cv2.findChessboardCorners(gray, CHECKERBOARD, None)
    
    if ret:
        successful_images += 1
        objpoints.append(objp)
        
        # ์ฝ”๋„ˆ ์ •๋ฐ€๋„ ํ–ฅ์ƒ
        corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
        imgpoints.append(corners2)
        
        # ์ฝ”๋„ˆ ์‹œ๊ฐํ™” (์„ ํƒ์‚ฌํ•ญ)
        img_with_corners = cv2.drawChessboardCorners(img, CHECKERBOARD, corners2, ret)
        cv2.imshow('Corners', img_with_corners)
        cv2.waitKey(100)
    else:
        failed_images.append(fname)
        print(f"์ฝ”๋„ˆ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Œ: {fname}")

cv2.destroyAllWindows()
print(f"์„ฑ๊ณต์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋œ ์ด๋ฏธ์ง€: {successful_images}์žฅ")
print(f"์‹คํŒจํ•œ ์ด๋ฏธ์ง€: {len(failed_images)}์žฅ")

if successful_images < 10:
    print("๊ฒฝ๊ณ : ์ผˆ๋ฆฌ๋ธŒ๋ ˆ์ด์…˜์„ ์œ„ํ•ด ์ตœ์†Œ 10์žฅ์˜ ์ด๋ฏธ์ง€๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.")
    exit()

# ์นด๋ฉ”๋ผ ์ผˆ๋ฆฌ๋ธŒ๋ ˆ์ด์…˜ ์ˆ˜ํ–‰
print("์นด๋ฉ”๋ผ ์ผˆ๋ฆฌ๋ธŒ๋ ˆ์ด์…˜์„ ์ˆ˜ํ–‰ํ•˜๋Š” ์ค‘...")
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)

# ๊ฒฐ๊ณผ ์ถœ๋ ฅ
print(f"์ผˆ๋ฆฌ๋ธŒ๋ ˆ์ด์…˜ ์™„๋ฃŒ!")
print(f"์žฌํˆฌ์˜ ์˜ค์ฐจ (RMS): {ret:.4f}")
print(f"์นด๋ฉ”๋ผ ๋งคํŠธ๋ฆญ์Šค:\n{mtx}")
print(f"์™œ๊ณก ๊ณ„์ˆ˜:\n{dist}")

# ๊ฒฐ๊ณผ ์ €์žฅ
calibration_data = {
    'camera_matrix': mtx,
    'distortion_coefficients': dist,
    'rotation_vectors': rvecs,
    'translation_vectors': tvecs,
    'rms_error': ret
}

with open('camera_calibration.pkl', 'wb') as f:
    pickle.dump(calibration_data, f)

print("์ผˆ๋ฆฌ๋ธŒ๋ ˆ์ด์…˜ ๋ฐ์ดํ„ฐ๊ฐ€ 'camera_calibration.pkl'์— ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")

# ์™œ๊ณก ๋ณด์ • ํ…Œ์ŠคํŠธ
test_img = cv2.imread(images[0])
h, w = test_img.shape[:2]
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w, h), 1, (w, h))

# ์™œ๊ณก ๋ณด์ • ์ ์šฉ
dst = cv2.undistort(test_img, mtx, dist, None, newcameramtx)

# ๊ฒฐ๊ณผ ๋น„๊ต ํ‘œ์‹œ
comparison = np.hstack((test_img, dst))
cv2.imshow('Original vs Undistorted', comparison)
cv2.waitKey(0)
cv2.destroyAllWindows()

์ผˆ๋ฆฌ๋ธŒ๋ ˆ์ด์…˜ ํ’ˆ์งˆ ํ™•์ธ

์ข‹์€ ์ผˆ๋ฆฌ๋ธŒ๋ ˆ์ด์…˜์˜ ์ง€ํ‘œ

  • RMS ์˜ค์ฐจ๊ฐ€ 1.0 ๋ฏธ๋งŒ

  • ์ด๋ฏธ์ง€ ์™œ๊ณก์ด ๋ˆˆ์— ๋„๊ฒŒ ๊ฐœ์„ ๋จ

  • ์ง์„ ์ด ์‹ค์ œ๋กœ ์ง์„ ์œผ๋กœ ๋ณด์ž„

์ผˆ๋ฆฌ๋ธŒ๋ ˆ์ด์…˜ ๊ฐœ์„  ๋ฐฉ๋ฒ•

  • ๋” ๋งŽ์€ ์ด๋ฏธ์ง€ ์ดฌ์˜

  • ๋‹ค์–‘ํ•œ ๊ฑฐ๋ฆฌ์—์„œ ์ดฌ์˜

  • ํ™”๋ฉด ์ „์ฒด ์˜์—ญ์„ ๊ณ ๋ฅด๊ฒŒ ์ปค๋ฒ„


3๋‹จ๊ณ„: ArUco ๋งˆ์ปค ์ธ์‹

์ผˆ๋ฆฌ๋ธŒ๋ ˆ์ด์…˜๋œ ์นด๋ฉ”๋ผ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ArUco ๋งˆ์ปค๋ฅผ ์ธ์‹ํ•˜๊ณ  ํฌ์ฆˆ๋ฅผ ์ถ”์ •ํ•ฉ๋‹ˆ๋‹ค.

ArUco ๋งˆ์ปค ์ธ์‹ ์Šคํฌ๋ฆฝํŠธ

import cv2
import numpy as np
import pickle

# ์ผˆ๋ฆฌ๋ธŒ๋ ˆ์ด์…˜ ๋ฐ์ดํ„ฐ ๋กœ๋“œ
with open('camera_calibration.pkl', 'rb') as f:
    calibration_data = pickle.load(f)

camera_matrix = calibration_data['camera_matrix']
dist_coeffs = calibration_data['distortion_coefficients']

# ArUco ์„ค์ •
aruco_dict = cv2.aruco.Dictionary_get(cv2.aruco.DICT_4X4_50)
parameters = cv2.aruco.DetectorParameters_create()

# ๋งˆ์ปค ํฌ๊ธฐ (์‹ค์ œ ํฌ๊ธฐ, ๋‹จ์œ„: ๋ฏธํ„ฐ)
marker_size = 0.05  # 5cm = 0.05m

# ์นด๋ฉ”๋ผ ์ดˆ๊ธฐํ™”
cap = cv2.VideoCapture(0)

print("ArUco ๋งˆ์ปค ์ธ์‹์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.")
print("'q' ํ‚ค๋ฅผ ๋ˆŒ๋Ÿฌ ์ข…๋ฃŒ")

while True:
    ret, frame = cap.read()
    if not ret:
        break
    
    # ์™œ๊ณก ๋ณด์ •
    frame = cv2.undistort(frame, camera_matrix, dist_coeffs, None, camera_matrix)
    
    # ๊ทธ๋ ˆ์ด์Šค์ผ€์ผ ๋ณ€ํ™˜
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    
    # ArUco ๋งˆ์ปค ๊ฒ€์ถœ
    corners, ids, rejected = cv2.aruco.detectMarkers(gray, aruco_dict, parameters=parameters)
    
    if ids is not None:
        # ๋งˆ์ปค ๊ทธ๋ฆฌ๊ธฐ
        cv2.aruco.drawDetectedMarkers(frame, corners, ids)
        
        # ํฌ์ฆˆ ์ถ”์ •
        rvecs, tvecs, _ = cv2.aruco.estimatePoseSingleMarkers(
            corners, marker_size, camera_matrix, dist_coeffs)
        
        for i in range(len(ids)):
            # ์ขŒํ‘œ์ถ• ๊ทธ๋ฆฌ๊ธฐ
            cv2.aruco.drawAxis(frame, camera_matrix, dist_coeffs, 
                             rvecs[i], tvecs[i], 0.03)
            
            # ๋งˆ์ปค ์ค‘์‹ฌ์  ๊ณ„์‚ฐ
            marker_center = np.mean(corners[i][0], axis=0).astype(int)
            
            # ๊ฑฐ๋ฆฌ ๊ณ„์‚ฐ (์œ ํด๋ฆฌ๋“œ ๊ฑฐ๋ฆฌ)
            distance = np.linalg.norm(tvecs[i][0])
            
            # ์ •๋ณด ํ‘œ์‹œ
            cv2.putText(frame, f"ID: {ids[i][0]}", 
                       (marker_center[0] - 50, marker_center[1] - 50),
                       cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
            
            cv2.putText(frame, f"Distance: {distance:.2f}m", 
                       (marker_center[0] - 50, marker_center[1] - 20),
                       cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
            
            # ๋งˆ์ปค ์ค‘์‹ฌ์— ์› ๊ทธ๋ฆฌ๊ธฐ
            cv2.circle(frame, tuple(marker_center), 5, (0, 0, 255), -1)
    
    # ๊ฒฐ๊ณผ ํ‘œ์‹œ
    cv2.imshow('ArUco Marker Detection', frame)
    
    # ์ข…๋ฃŒ ์กฐ๊ฑด
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()

ArUco ๋งˆ์ปค ์ธ์‹ ์ตœ์ ํ™”

์ธ์‹ ์„ฑ๋Šฅ ํ–ฅ์ƒ ๋ฐฉ๋ฒ•

  • ์ ์ ˆํ•œ ์กฐ๋ช… ํ™˜๊ฒฝ ์œ ์ง€

  • ๋งˆ์ปค๊ฐ€ ํ™”๋ฉด์— ํ‰ํ–‰ํ•˜๊ฒŒ ์œ„์น˜

  • ๋งˆ์ปค์™€ ์นด๋ฉ”๋ผ ๊ฐ„ ์ ์ ˆํ•œ ๊ฑฐ๋ฆฌ ์œ ์ง€

๋ฌธ์ œ ํ•ด๊ฒฐ

  • ๋งˆ์ปค๊ฐ€ ์ธ์‹๋˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ: ์กฐ๋ช… ํ™•์ธ, ๋งˆ์ปค ์ƒํƒœ ์ ๊ฒ€

  • ๊ฑฐ๋ฆฌ ๊ฐ’์ด ๋ถ€์ •ํ™•ํ•œ ๊ฒฝ์šฐ: ์ผˆ๋ฆฌ๋ธŒ๋ ˆ์ด์…˜ ์žฌ์ˆ˜ํ–‰, ๋งˆ์ปค ํฌ๊ธฐ ์žฌ์ธก์ •


4๋‹จ๊ณ„: ๊ฑฐ๋ฆฌ์— ๋”ฐ๋ฅธ ๊ฒฝ๊ณ ๋ฌธ ์ถœ๋ ฅ

์ธก์ •๋œ ๊ฑฐ๋ฆฌ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ๊ฒฝ๊ณ  ์‹œ์Šคํ…œ์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

๊ฑฐ๋ฆฌ ๊ธฐ๋ฐ˜ ๊ฒฝ๊ณ  ์‹œ์Šคํ…œ ์Šคํฌ๋ฆฝํŠธ

import cv2
import numpy as np
import pickle
import time

# ์ผˆ๋ฆฌ๋ธŒ๋ ˆ์ด์…˜ ๋ฐ์ดํ„ฐ ๋กœ๋“œ
with open('camera_calibration.pkl', 'rb') as f:
    calibration_data = pickle.load(f)

camera_matrix = calibration_data['camera_matrix']
dist_coeffs = calibration_data['distortion_coefficients']

# ArUco ์„ค์ •
aruco_dict = cv2.aruco.Dictionary_get(cv2.aruco.DICT_4X4_50)
parameters = cv2.aruco.DetectorParameters_create()

# ๋งˆ์ปค ํฌ๊ธฐ (์‹ค์ œ ํฌ๊ธฐ, ๋‹จ์œ„: ๋ฏธํ„ฐ)
marker_size = 0.05  # 5cm

# ๊ฑฐ๋ฆฌ ์ž„๊ณ„๊ฐ’ ์„ค์ • (๋ฏธํ„ฐ)
WARNING_DISTANCES = {
    'DANGER': 0.2,    # 20cm ์ดํ•˜ - ์œ„ํ—˜
    'CAUTION': 0.5,   # 50cm ์ดํ•˜ - ์ฃผ์˜
    'SAFE': 1.0       # 1m ์ด์ƒ - ์•ˆ์ „
}

# ๊ฒฝ๊ณ  ์ƒ‰์ƒ ์„ค์ •
WARNING_COLORS = {
    'DANGER': (0, 0, 255),    # ๋นจ๊ฐ„์ƒ‰
    'CAUTION': (0, 165, 255), # ์ฃผํ™ฉ์ƒ‰
    'SAFE': (0, 255, 0)       # ์ดˆ๋ก์ƒ‰
}

# ์นด๋ฉ”๋ผ ์ดˆ๊ธฐํ™”
cap = cv2.VideoCapture(0)

# ์•Œ๋ฆผ ๊ด€๋ จ ๋ณ€์ˆ˜
last_warning_time = 0
warning_interval = 2.0  # 2์ดˆ๋งˆ๋‹ค ๊ฒฝ๊ณ ์Œ

def get_warning_level(distance):
    """๊ฑฐ๋ฆฌ์— ๋”ฐ๋ฅธ ๊ฒฝ๊ณ  ๋ ˆ๋ฒจ ๋ฐ˜ํ™˜"""
    if distance <= WARNING_DISTANCES['DANGER']:
        return 'DANGER'
    elif distance <= WARNING_DISTANCES['CAUTION']:
        return 'CAUTION'
    else:
        return 'SAFE'

def draw_warning_overlay(frame, warning_level, distance):
    """๊ฒฝ๊ณ  ์˜ค๋ฒ„๋ ˆ์ด ๊ทธ๋ฆฌ๊ธฐ"""
    height, width = frame.shape[:2]
    
    # ๊ฒฝ๊ณ  ๋ฉ”์‹œ์ง€ ์„ค์ •
    if warning_level == 'DANGER':
        message = "DANGER! TOO CLOSE!"
        font_scale = 2.0
    elif warning_level == 'CAUTION':
        message = "CAUTION! BE CAREFUL!"
        font_scale = 1.5
    else:
        message = "SAFE DISTANCE"
        font_scale = 1.0
    
    color = WARNING_COLORS[warning_level]
    
    # ๋ฐฐ๊ฒฝ ์‚ฌ๊ฐํ˜• ๊ทธ๋ฆฌ๊ธฐ
    overlay = frame.copy()
    cv2.rectangle(overlay, (0, 0), (width, 100), color, -1)
    cv2.addWeighted(overlay, 0.3, frame, 0.7, 0, frame)
    
    # ํ…์ŠคํŠธ ํฌ๊ธฐ ๊ณ„์‚ฐ
    text_size = cv2.getTextSize(message, cv2.FONT_HERSHEY_SIMPLEX, font_scale, 3)[0]
    text_x = (width - text_size[0]) // 2
    text_y = 60
    
    # ํ…์ŠคํŠธ ๊ทธ๋ฆฌ๊ธฐ (๊ฒ€์€์ƒ‰ ์™ธ๊ณฝ์„ )
    cv2.putText(frame, message, (text_x-2, text_y-2), 
               cv2.FONT_HERSHEY_SIMPLEX, font_scale, (0, 0, 0), 5)
    cv2.putText(frame, message, (text_x, text_y), 
               cv2.FONT_HERSHEY_SIMPLEX, font_scale, (255, 255, 255), 3)
    
    # ๊ฑฐ๋ฆฌ ์ •๋ณด ํ‘œ์‹œ
    distance_text = f"Distance: {distance:.2f}m"
    cv2.putText(frame, distance_text, (20, height - 30), 
               cv2.FONT_HERSHEY_SIMPLEX, 1.0, color, 2)

def play_warning_sound(warning_level):
    """๊ฒฝ๊ณ ์Œ ์ถœ๋ ฅ (์‹œ์Šคํ…œ์— ๋”ฐ๋ผ ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์Œ)"""
    global last_warning_time
    current_time = time.time()
    
    if current_time - last_warning_time > warning_interval:
        if warning_level == 'DANGER':
            # ์—ฐ์† ๋น„ํ”„์Œ (์œ„ํ—˜)
            print("\a" * 3)  # ์‹œ์Šคํ…œ ์•Œ๋ฆผ์Œ
        elif warning_level == 'CAUTION':
            # ๋‹จ์ผ ๋น„ํ”„์Œ (์ฃผ์˜)
            print("\a")
        
        last_warning_time = current_time

print("๊ฑฐ๋ฆฌ ๊ธฐ๋ฐ˜ ๊ฒฝ๊ณ  ์‹œ์Šคํ…œ์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.")
print("๊ฒฝ๊ณ  ๋ ˆ๋ฒจ:")
print(f"- ์œ„ํ—˜ (๋นจ๊ฐ•): {WARNING_DISTANCES['DANGER']}m ์ดํ•˜")
print(f"- ์ฃผ์˜ (์ฃผํ™ฉ): {WARNING_DISTANCES['CAUTION']}m ์ดํ•˜")
print(f"- ์•ˆ์ „ (์ดˆ๋ก): {WARNING_DISTANCES['SAFE']}m ์ด์ƒ")
print("'q' ํ‚ค๋ฅผ ๋ˆŒ๋Ÿฌ ์ข…๋ฃŒ")

while True:
    ret, frame = cap.read()
    if not ret:
        break
    
    # ์™œ๊ณก ๋ณด์ •
    frame = cv2.undistort(frame, camera_matrix, dist_coeffs, None, camera_matrix)
    
    # ๊ทธ๋ ˆ์ด์Šค์ผ€์ผ ๋ณ€ํ™˜
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    
    # ArUco ๋งˆ์ปค ๊ฒ€์ถœ
    corners, ids, rejected = cv2.aruco.detectMarkers(gray, aruco_dict, parameters=parameters)
    
    if ids is not None:
        # ๋งˆ์ปค ๊ทธ๋ฆฌ๊ธฐ
        cv2.aruco.drawDetectedMarkers(frame, corners, ids)
        
        # ํฌ์ฆˆ ์ถ”์ •
        rvecs, tvecs, _ = cv2.aruco.estimatePoseSingleMarkers(
            corners, marker_size, camera_matrix, dist_coeffs)
        
        # ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ๋งˆ์ปค ์ฐพ๊ธฐ
        min_distance = float('inf')
        closest_marker_idx = 0
        
        for i in range(len(ids)):
            distance = np.linalg.norm(tvecs[i][0])
            if distance < min_distance:
                min_distance = distance
                closest_marker_idx = i
            
            # ์ขŒํ‘œ์ถ• ๊ทธ๋ฆฌ๊ธฐ
            cv2.aruco.drawAxis(frame, camera_matrix, dist_coeffs, 
                             rvecs[i], tvecs[i], 0.03)
            
            # ๋งˆ์ปค ์ค‘์‹ฌ์ ๊ณผ ID ํ‘œ์‹œ
            marker_center = np.mean(corners[i][0], axis=0).astype(int)
            cv2.putText(frame, f"ID: {ids[i][0]}", 
                       (marker_center[0] - 30, marker_center[1] + 30),
                       cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
        
        # ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ๋งˆ์ปค์— ๋Œ€ํ•œ ๊ฒฝ๊ณ  ์ฒ˜๋ฆฌ
        warning_level = get_warning_level(min_distance)
        
        # ๊ฒฝ๊ณ  ์˜ค๋ฒ„๋ ˆ์ด ๊ทธ๋ฆฌ๊ธฐ
        draw_warning_overlay(frame, warning_level, min_distance)
        
        # ๊ฒฝ๊ณ ์Œ ์ถœ๋ ฅ
        if warning_level in ['DANGER', 'CAUTION']:
            play_warning_sound(warning_level)
    
    else:
        # ๋งˆ์ปค๊ฐ€ ์—†์„ ๋•Œ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ
        cv2.putText(frame, "No ArUco marker detected", (20, 50),
                   cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 0, 255), 2)
    
    # ์‚ฌ์šฉ๋ฒ• ์•ˆ๋‚ด
    cv2.putText(frame, "Press 'q' to quit", (20, frame.shape[0] - 10),
               cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 1)
    
    # ๊ฒฐ๊ณผ ํ‘œ์‹œ
    cv2.imshow('Distance Warning System', frame)
    
    # ์ข…๋ฃŒ ์กฐ๊ฑด
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()
print("๊ฑฐ๋ฆฌ ๊ฒฝ๊ณ  ์‹œ์Šคํ…œ์„ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค.")

๊ฒฝ๊ณ  ์‹œ์Šคํ…œ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•

๊ฑฐ๋ฆฌ ์ž„๊ณ„๊ฐ’ ์กฐ์ •

# ์šฉ๋„์— ๋งž๊ฒŒ ์ž„๊ณ„๊ฐ’ ๋ณ€๊ฒฝ
WARNING_DISTANCES = {
    'DANGER': 0.1,    # 10cm - ๋งค์šฐ ์œ„ํ—˜
    'CAUTION': 0.3,   # 30cm - ์ฃผ์˜ ํ•„์š”
    'SAFE': 0.8       # 80cm - ์•ˆ์ „ ๊ฑฐ๋ฆฌ
}

์ถ”๊ฐ€ ๊ธฐ๋Šฅ ๊ตฌํ˜„ ์•„์ด๋””์–ด

  • ๋กœ๊ทธ ํŒŒ์ผ์— ๊ฑฐ๋ฆฌ ๋ฐ์ดํ„ฐ ๊ธฐ๋ก

  • ๋„คํŠธ์›Œํฌ๋ฅผ ํ†ตํ•œ ์›๊ฒฉ ์•Œ๋ฆผ

  • ๋‹ค์ค‘ ๋งˆ์ปค ๋™์‹œ ๋ชจ๋‹ˆํ„ฐ๋ง

  • ๊ฑฐ๋ฆฌ ๋ณ€ํ™”์œจ ๊ธฐ๋ฐ˜ ๊ฒฝ๊ณ 


๋ฌธ์ œ ํ•ด๊ฒฐ ๊ฐ€์ด๋“œ

์ผ๋ฐ˜์ ์ธ ๋ฌธ์ œ๋“ค

๋งˆ์ปค๊ฐ€ ์ธ์‹๋˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ
  • ์กฐ๋ช… ์ƒํƒœ ํ™•์ธ (๋„ˆ๋ฌด ๋ฐ๊ฑฐ๋‚˜ ์–ด๋‘์šฐ๋ฉด ์•ˆ๋จ)

  • ๋งˆ์ปค์˜ ๋ฌผ๋ฆฌ์  ์ƒํƒœ ์ ๊ฒ€ (๊ตฌ๊ฒจ์ง€๊ฑฐ๋‚˜ ๋”๋Ÿฌ์›Œ์ง)

  • ์นด๋ฉ”๋ผ ํฌ์ปค์Šค ์กฐ์ •

  • ArUco ๋”•์…”๋„ˆ๋ฆฌ ํƒ€์ž… ํ™•์ธ

๊ฑฐ๋ฆฌ ์ธก์ •์ด ๋ถ€์ •ํ™•ํ•œ ๊ฒฝ์šฐ
  • ์นด๋ฉ”๋ผ ์ผˆ๋ฆฌ๋ธŒ๋ ˆ์ด์…˜ ์žฌ์ˆ˜ํ–‰

  • ๋งˆ์ปค ํฌ๊ธฐ ์žฌ์ธก์ • (์ •ํ™•ํ•œ ๋ฌผ๋ฆฌ์  ํฌ๊ธฐ ํ•„์š”)

  • ์ผˆ๋ฆฌ๋ธŒ๋ ˆ์ด์…˜ ์‹œ ์‚ฌ์šฉํ•œ ์ด๋ฏธ์ง€ ํ’ˆ์งˆ ํ™•์ธ

์„ฑ๋Šฅ์ด ๋А๋ฆฐ ๊ฒฝ์šฐ
  • ์ด๋ฏธ์ง€ ํ•ด์ƒ๋„ ๋‚ฎ์ถ”๊ธฐ

  • ์นด๋ฉ”๋ผ FPS ์กฐ์ •

  • ๋ถˆํ•„์š”ํ•œ ์ฒ˜๋ฆฌ ๊ณผ์ • ์ œ๊ฑฐ

์ •ํ™•๋„ ํ–ฅ์ƒ ๋ฐฉ๋ฒ•

์ผˆ๋ฆฌ๋ธŒ๋ ˆ์ด์…˜ ํ’ˆ์งˆ ๊ฐœ์„ 
  • ๋” ๋งŽ์€ ์ผˆ๋ฆฌ๋ธŒ๋ ˆ์ด์…˜ ์ด๋ฏธ์ง€ ์‚ฌ์šฉ (20์žฅ ์ด์ƒ)

  • ๋‹ค์–‘ํ•œ ๊ฐ๋„์™€ ๊ฑฐ๋ฆฌ์—์„œ ์ดฌ์˜

  • ๊ณ ํ•ด์ƒ๋„ ์ด๋ฏธ์ง€ ์‚ฌ์šฉ

๋งˆ์ปค ์ธ์‹ ์ •ํ™•๋„ ํ–ฅ์ƒ
  • ๊ณ ํ’ˆ์งˆ ๋งˆ์ปค ์ธ์‡„ (์„ ๋ช…ํ•˜๊ณ  ๋Œ€๋น„๊ฐ€ ๋šœ๋ ทํ•จ)

  • ์ ์ ˆํ•œ ๋งˆ์ปค ํฌ๊ธฐ ์„ ํƒ (๋„ˆ๋ฌด ์ž‘๊ฑฐ๋‚˜ ํฌ์ง€ ์•Š๊ฒŒ)

  • ๊ท ์ผํ•œ ์กฐ๋ช… ํ™˜๊ฒฝ ์กฐ์„ฑ


์ฐธ๊ณ  ์ž๋ฃŒ

OpenCV ๊ณต์‹ ๋ฌธ์„œ

์ถ”๊ฐ€ ํ•™์Šต ์ž๋ฃŒ

GitHub ๋ ˆํฌ์ง€ํ† ๋ฆฌ