본문 바로가기

프로그래밍 이야기

"우리 로켓은 안물어요" - 사무실용 이지스 시스템 구축(1)

먼저 동작화면..

 

 

한동안 유행하던 사무실용 USB 로켓 런쳐들이 있습니다. 대략 윈도우 XP시대의 것들이 대부분이고 요즘에는 거의 단종되었습니다. PC에 연결하면 아래와 같은 원시적인 클라이언트 프로그램을 통해 상하좌우로 로켓을 이동시키고 로켓을 발사할수 있게 해줍니다.

 

 

제가 구매했던건 Brookstone의 것인데, 회사도 망했고 제품도 망했고, 아무런 지원을 받을수 없습니다. 꿈도 희망도 없는 상태..

 

 

초저해상도 카메라가 내장되어 있습니다.

 

USB를 꽂으니 로켓이 잡히고....

 

카메라도 잡힙니다.

 

 

카메라의 성능 테스트를 위해 OpenCV를 이용해보니 조명의 상황에 따라 변화가 무쌍하고 프레임들이 깔끔하지 않아 인식률이 낮긴 하지만 불가능하지는 않습니다. 나중에 별도의 고해상도 캠을 따로 연결하던가, 멀티 사이트 카메라 네트웍을 구성해서 위치 인식시키고 그러면 인식률과 적중률은 올라갈 것으로 봅니다.

 

 

명령을 내릴때마다 USB를 통해 나가는 명령들을 정리해보니 대략 아래와 같습니다.

좌로 [0x02, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
우로 [0x02, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
아래로 [0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
위로 [0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
발사 [0x02, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]

정지 [0x02, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]

 

문제는 이 명령들 하나를 전송하면 로켓런쳐는 계속 그쪽으로 포탑을 돌립니다. 반드시 일정 시간이 지난 후에 정지 명령을 줘야만 원하는 위치에 정지시킬수 있습니다. 따라서 포탑의 이동 명령은 동작시간을 인자로 받아 아래의 형식으로 구현했습니다.

 

    my_rocket_device.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
    # HOLD
    time.sleep(seconds/ 1000.0)
    # STOP
    my_rocket_device.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])



실험을 통해 제일 좌측에서 우측까지의 이동시간은 대략 8000ms, 상하 이동한계는 1000ms라는걸 알아냈습니다. 따라서 초기화를 할때 이를 감안해서 초기 위치로 이동시켜주면서 현재의 위치를 대략 기억하면 됩니다.

 

평상시(==얼굴이 보이지 않을때)에는 그냥 좌에서 우로 우에서 좌로 적당히 패트롤링을 하도록 했습니다.

 

사람의 얼굴 감지는 OpenCV를 통하면 손쉽게 얻을수 있습니다. 여기에서는 face_detection 라이브러리를 사용합니다.

사람의 얼굴을 발견하면 그 이동에 따라 추적하면서 (처리속도가 느려서 놓치기도 합니다만) 나름의 위협 평가를 합니다. 현재는 간단하게 구현..

 

위협 평가 결과 위험하다는 판단이 되면 바로 미사일을 발사합니다.

탄착점은 실제로 맞아가며 테스트를 해봤습니다. ^^

 

다음 버전에서는 사내 업무 시스템을 통하여 조직도와 연동을 해보면 재미있을것 같습니다. 미리 등록해놓은 사무실 빌런인지, 결재라인상의 인물인지 등의 정보를 기반으로 위협레벨을 판정한다던지.. ㅋㅋㅋㅋ

 

"착한 임직원은 쏘지 않아요. 인식오류만 아니면.. 인식하기 좋게 얼굴들 펴고 다니시죠" ㅋㅋㅋ

 

 

소스는 대략 아래와 같습니다.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import usb
import cv2
import sys
import random
import time
import face_recognition

print time.time(),"BEGIN INITIALIZING.."
print time.time(),"INITIALIZING USB.."

# USB 초기화 하자!!
my_rocket_device = usb.core.find(idVendor=0x2123, idProduct=0x1010)
try:
    my_rocket_device.detach_kernel_driver(0)
except:
    pass                        # Never Mind!
my_rocket_device.set_configuration()
# USB 초기화 끝!
print time.time(),"USB INITIALIZED.."


# 포탑 제어 함수들.
# 포탑의 제어 범위는 대략 좌우로 8000ms, 상하로 1000ms

# 포탑을 좌로 돌림 (ms 단위임)
def turn_left(seconds):
    # LEFT
    my_rocket_device.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
    # HOLD
    time.sleep(seconds/ 1000.0)
    # STOP
    my_rocket_device.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
    
# 포탑을 우로 돌림 (ms 단위)
def turn_right(seconds):
    # RIGHT
    my_rocket_device.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
    # HOLD
    time.sleep(seconds/ 1000.0)
    # STOP
    my_rocket_device.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])

# 포탑을 아래로 내림 (ms 단위)
def look_down(seconds):
    # DOWN
    my_rocket_device.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
    # HOLD
    time.sleep(seconds/ 1000.0)
    # STOP
    my_rocket_device.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])

# 포탑을 위로 올림 (ms 단위)
def look_up(seconds):
    # DOWN
    my_rocket_device.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
    # HOLD
    time.sleep(seconds/ 1000.0)
    # STOP
    my_rocket_device.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])

# 포탑의 0점 맞추기. 가능한 좌하단으로 밀어버림.
def goto_zero_point():
    look_down(2000)
    turn_left(8000)

# 로켓 발사!!!
def fire_rocket():
    time.sleep(0.5)             # 바로 직전의 이동이 있었다면 약간의 시간을 줘서 안정화.

    # 발사 명령 0x10 
    my_rocket_device.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])

    # 잔심? ㅋㅋㅋㅋ
    time.sleep(4)

print time.time(),"INITIALIZING OPENCV"

# 영상 캡쳐 장치 확보 
video_capture = cv2.VideoCapture(0)

# 하드코딩.. 가로세로 604x480
screen_size = (640, 480)

# 조준점 위치
crosshair_position = (320, 240)

# 표적의 좌우 허용치
tolerance = (64, 30)

# 얼만큼 단위로 움직일건지..
INCREMENT = 15

# 영상 보여주는 화면 확대비율
FRAME_SCALE = 1.9


print time.time(),"INITIALIZING TURRET STATUS"

# 현재의 패트롤 상태
patrol_status = 0
patrol_count = 0

# 포탑의 현재 위치. 좌하단이 0,0
turret_x = 0
turret_y = 0

# 포탑을 일단 좌하단으로 옮긴다.
#goto_zero_point()
print time.time(),"TURRET STATUS INITIALIZED.."

# 패트롤 모드 들어가기 전에 포탑의 높이를 중간쯤으로 올리자.
print time.time(),"ADJUSTING TURRET POSITION FOR PATROL"
look_up(200)
turret_y = turret_y + 200

print time.time(),"##### BEGIN PATROL #####"

do_we_have_to_process_this_frame = 0

while True:
    print time.time(),"CURRENT TURRET POSITION ( %d, %d )"%(turret_x, turret_y)

    # 한 프레임 읽어오세!
    ret, frame = video_capture.read()

    small_frame = cv2.resize(frame, (0, 0), fx=0.25, fy=0.25)
    rgb_small_frame = small_frame[:, :, ::-1]
    
    if not do_we_have_to_process_this_frame:
        do_we_have_to_process_this_frame = do_we_have_to_process_this_frame + 1
        if do_we_have_to_process_this_frame >=4:
            do_we_have_to_process_this_frame = 0
        print 'NOT PROCESSING'
        continue
    print 'PROCESSING'

    face_locations = face_recognition.face_locations(rgb_small_frame)
    face_encodings = face_recognition.face_encodings(rgb_small_frame, face_locations)

    for (x, y, w, h), face_encoding in zip(face_locations, face_encodings):
        suspicious = random.randrange(1,200) > 197
        if suspicious:
            print time.time(),"THREAT ASSESSMENT = DANGER"
        else:
            print time.time(),"THREAT ASSESSMENT = SAFE"

        if suspicious:          # FIRE!!!
            print time.time(),"##############FIRE####################"
            cv2.putText(frame, 'FIRE!!!!!!', (10, 460), cv2.FONT_HERSHEY_SIMPLEX, 3, (0,0,255), 2)
            # Draw Crosshair
            cv2.line(frame, (crosshair_position[0], crosshair_position[1]-10),
                     (crosshair_position[0], crosshair_position[1]+10),
                     (0,0,255), 4)
            cv2.line(frame, (crosshair_position[0]-10, crosshair_position[1]),
                     (crosshair_position[0]+10, crosshair_position[1]),
                     (0,0,255), 4)

            cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)
            show_frame = cv2.resize(frame, None, fx=FRAME_SCALE, fy=FRAME_SCALE, interpolation=cv2.INTER_CUBIC)
            cv2.imshow('Video', show_frame)

            if cv2.waitKey(1) & 0xFF == ord('q'):
                break
            fire_rocket()

        else:
            print time.time(),"## TARGET!"
            cv2.putText(frame, 'TARGET ACQUIRED!', (10, 460), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0,200,255), 2)
            # Draw Crosshair
            cv2.line(frame, (crosshair_position[0], crosshair_position[1]-10),
                     (crosshair_position[0], crosshair_position[1]+10),
                     (0,200,255), 4)
            cv2.line(frame, (crosshair_position[0]-10, crosshair_position[1]),
                     (crosshair_position[0]+10, crosshair_position[1]),
                     (0,200,255), 4)

            cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)
            center_point = (int((x+x+w)/2), int((y+y+h)/2))
            print time.time(),"CENTER POINT IS ",center_point
            if center_point[0] < crosshair_position[0]-tolerance[0]:
                distance = int((crosshair_position[0] - center_point[0])/6)
                turn_left(distance)
                turret_x = turret_x - distance
            elif center_point[0] > crosshair_position[0]+tolerance[0]:
                distance = int((center_point[0] - crosshair_position[0])/6)
                turn_right(distance)
                turret_x = turret_x + distance
            if center_point[1] < crosshair_position[1]-tolerance[1]:
                distance = int((crosshair_position[1] - center_point[1])/6)
                look_up(distance)
                turret_y = turret_y + distance
                
            elif center_point[1] > crosshair_position[1]+tolerance[1]:
                distance = int((center_point[1] - crosshair_position[1])/6)
                look_down(distance)
                turret_y = turret_y - distance

    if not len(face_locations): # patrol
        print time.time(),"PATROLLING"
        cv2.putText(frame, 'Patrolling...', (10, 460), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255,255,255), 2)
        # Draw Crosshair
        cv2.line(frame, (crosshair_position[0], crosshair_position[1]-10),
                 (crosshair_position[0], crosshair_position[1]+10),
                 (255,255,255), 4)
        cv2.line(frame, (crosshair_position[0]-10, crosshair_position[1]),
                 (crosshair_position[0]+10, crosshair_position[1]),
                 (255,255,255), 4)

        patrol_count = patrol_count + 1
        print time.time(),"PATROL_COUNT=",patrol_count, "PATROL_STATUS=",patrol_status
        if patrol_count > 5:
            patrol_count = 0
            if patrol_status > 8:
                patrol_status = -8
            patrol_status = patrol_status + 1
            if patrol_status > 0:
                turn_right(60)
                turret_x = turret_x +60
            else:
                turn_left(60)
                turret_x = turret_x -60
            
    # Display the resulting frame
    show_frame = cv2.resize(frame, None, fx=FRAME_SCALE, fy=FRAME_SCALE, interpolation=cv2.INTER_CUBIC)
    cv2.imshow('Video', show_frame)

    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# When everything is done, release the capture
video_capture.release()
cv2.destroyAllWindows()
320x100