본문 바로가기
라즈베리 파이

[라즈베리 파이] 라즈베리파이를 이용하여 스마트폰 카메라 움직임 감지 장치 만들기(CCTV)

by HI_Ai 2023. 9. 4.
반응형

라즈베리파이와 스마트폰을 이용하여 스마트폰 카메라에 움직임이 식별되면 이를 감지하는 방법에 대해 알아보겠습니다. 움직임 식별 시에는 RGB LED를 활용하여 불빛이 깜빡임을 토대로 움직임이 있다는 것을 알리도록 코드를 구현해 보았습니다.

별도 라즈베리 파이와 호환되는 카메라 모듈이 아닌 공기계(스마트폰)을 활용하는 방법입니다. 이를 활용하면 핸드폰 카메라를 CCTV처럼 이용할 수 있게 됩니다.

 

1. IP Webcam 사용(코드사용 X)

1.1. 핸드폰 카메라 스트리밍 설정

 - 안드로이드의 경우 IP Webcam과 같은 앱을 활용하여 핸드폰 카메라의 스트리밍 서버를 설정할 수 있습니다.

 - 앱을 다운로드 후 HTTP 주소를 통해 스트리밍을 합니다. ex) http://000.000.0.00:8080

 

1.2. 라즈베리파이에서 스트리밍 접근

 - 라즈베리 파이에 웹 브라우저를 설치하면 스트리밍을 바로 볼 수 있습니다.

sudo apt-get install chromium-browser 명령어를 통해 크로미움 브라우저를 설치합니다.

이후 라즈베리파이 브라우저를 열고 위에서 얻은 IP 주소와 포트 번호로 접속하여 스트리밍 확인이 가능합니다.

 


2. 라즈베리 파이 리눅스를 활용한 스마트폰 카메라 움직임 감지

2.1 필요한 패키지 설치

#### 시스템 업데이트 및 필요한 라이브러리 설치####
sudo apt-get update && sudo apt-get upgrade
sudo apt-get install build-essential cmake pkg-config
sudo apt-get install libjpeg-dev libtiff5-dev libjasper-dev libpng-dev
sudo apt-get install libavcodec-dev libavformat-dev libswscale-dev libv4l-dev
sudo apt-get install libxvidcore-dev libx264-dev
sudo apt-get install libfontconfig1-dev libcairo2-dev
sudo apt-get install libgdk-pixbuf2.0-dev libpango1.0-dev
sudo apt-get install libgtk2.0-dev libgtk-3-dev
sudo apt-get install libatlas-base-dev gfortran
sudo apt-get install python3-dev

####numpy 설치####
pip install numpy

####OpenCV 소스코드 다운로드 및 컴파일####
cd ~
wget -O opencv.zip https://github.com/opencv/opencv/archive/refs/tags/4.5.2.zip
wget -O opencv_contrib.zip https://github.com/opencv/opencv_contrib/archive/refs/tags/4.5.2.zip
unzip opencv.zip
unzip opencv_contrib.zip
mv opencv-4.5.2 opencv
mv opencv_contrib-4.5.2 opencv_contrib

#### 컴파일 위한 디렉토리 생성, CMake 실행####
cd ~/opencv
mkdir build
cd build
cmake -D CMAKE_BUILD_TYPE=RELEASE \
    -D CMAKE_INSTALL_PREFIX=/usr/local \
    -D OPENCV_EXTRA_MODULES_PATH=~/opencv_contrib/modules \
    -D ENABLE_NEON=ON \
    -D ENABLE_VFPV3=ON \
    -D BUILD_TESTS=OFF \
    -D INSTALL_PYTHON_EXAMPLES=OFF \
    -D OPENCV_ENABLE_NONFREE=ON \
    -D CMAKE_SHARED_LINKER_FLAGS=-latomic \
    -D BUILD_EXAMPLES=OFF ..

####OpenCV컴파일 및 설치####
make -j4
sudo make install
sudo ldconfig

####Python과 OpenCV 바인딩 확인 및 설정
cd /usr/local/lib/python3.X/dist-packages/cv2/python-3.X
sudo mv cv2.cpython-3Xm-arm-linux-gnueabihf.so cv2.so

 

이후 python 셀에서 import cv2를 실행하여 정상적으로 import가 가능한지 확인합니다.

python


import cv2
print(cv2.__version__)

 

정상적으로 version이 출력된다면 이제 아래 코드를 입력합니다.

 

2.2 코드 실행

입력 전 확인사항

 1. RGBLED를 라즈베리 파이에 연결(GPIO 이용)

 2. 스마트폰 IP webcam 연결 및 IP 주소 확인

 

import cv2
import RPi.GPIO as GPIO
import time

# GPIO 설정
GPIO.setwarnings(False) # GPIO 경고 메시지 출력하지 않음
GPIO.setmode(GPIO.BCM) # GPIO 핀 번호링 방식 BCM으로 설정
RED_LED = 17
GREEN_LED = 18
BLUE_LED = 27
GPIO.setup(RED_LED, GPIO.OUT) # GPIO 핀 17번 출력 모드로 설정
GPIO.setup(GREEN_LED, GPIO.OUT) # GPIO 핀 18번 출력 모드로 설정
GPIO.setup(BLUE_LED, GPIO.OUT) # GPIO 핀 27번 출력 모드로 설정

# LED 깜빡이기, R, G, B 순으로 0.1초씩 총 2번 반복하여 켜지기
def blink_led(): 
    for _ in range(2):
        GPIO.output(RED_LED, True)
        time.sleep(0.1)
        GPIO.output(RED_LED, False)
        GPIO.output(GREEN_LED, True)
        time.sleep(0.1)
        GPIO.output(GREEN_LED, False)
        GPIO.output(BLUE_LED, True)
        time.sleep(0.1)
        GPIO.output(BLUE_LED, False)

# 비디오 스트림하는 파일 캡처하는 객체 생성(URL에서 실시간 비디오 스트림 받음)
cap = cv2.VideoCapture("http://0.0.0.0:0000/video") # 본인 IP webcam의 IP 주소 작성

# 움직임 감지를 위한 면적 설정(움직임 감지시 변경된 영역 필셀 수가 100000이상이어야함)
AREA = 100000 #민감도 설정

last_frame = None # 이전 프레임 저장하는 변수(초기값 none), 현재 프레임과 차이 계산하여 움직임 감지
timestamp = time.time() # 현재 시간을 초 단위로 가져와 변수에 저장(파일 이름 생성시 사용)
last_save_time = 0
# 사진을 마지막으로 저장한 시간을 추적하는 변수, 초단위 값으로 초기화
# 1초마다 한번씩 사진 저장하도록 제한

while True:
    ret, frame = cap.read() # cap.read() 카메라에서 현재 프레임 읽음
    #  ret 프레임 읽기여부 확인(boolean), frame : 읽어온 동영상의 현재 프레임 타나냄
    if not ret: # ret = False 경우 아래 메시지 출력 후 루프 처음으로 돌아감
        print("Failed to grab frame")
        continue

    # 첫 프레임 설정, 첫 프레
    if last_frame is None:
        base_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 초기 프레임 그레이스케일 변환 
        base_frame = cv2.GaussianBlur(base_frame, (21, 21), 0)
        # 가우시안 블러 적용, 움직임 갖미 사용하는 참조 프레임
        last_frame = frame # last_frame에 현재 프레임을 저장

    gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 현재 프레임 frame을 그레이스케일로 변환
    # 그레이스케일 컬러 정보 없이 밝기 정보만으로 움직임 감지, 계산 빠르고 효율적
    gray_frame = cv2.GaussianBlur(gray_frame, (21, 21), 0) # 가우시안 블러 적용
    # 블러 처리는 이미지 노이즈 줄이고 움직임 감지시 오류 줄이는 데 도움
    # (21,21) 블러 커널 크기, 큰 값은 이미지 더 부드럽게 만들어줌

    frame_delta = cv2.absdiff(base_frame, gray_frame) # base_frame과 gray_frame의 차이 계산
    thresh = cv2.threshold(frame_delta, 25, 255, cv2.THRESH_BINARY)[1]
    # frame_delta(다양한 밝기 값 가진 픽셀 구성)을 이진화 차이 큰 픽셀 흰색, 작은 픽셀 검정색 변환
    thresh = cv2.dilate(thresh, None, iterations=2)
    # 이미지 흰색 부분 확장, 움직임 감지에서 작은 흰색 구간 연결 크게 만듦(노이즈 줄임, 실제움직임 명확)
    
    contours, _ = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
	# 흰색 영역의 외곽선(contour) 찾음, 움직임 결과로 생긴 픽셀 변화를 외곽선으로 나타냄
    motion_detected = False # 움직임 감지 여부 추적 위해 변수 초기화
    for contour in contours: # 모든 외곽선에 대해 작업 반복
        if cv2.contourArea(contour) < AREA: # 외곽선 둘러싼 영역 면적 계산
            continue
        (x, y, w, h) = cv2.boundingRect(contour) # 외곽선 감싸는 최소 크기 사각형의 좌표 크기 반환
        cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2) # 움직임 감지된 영역에 초록색 사각형 그림
        motion_detected = True # 움직임 감지되었음을 나타내기 위해 변수를 True로 설정

    # 움직임이 감지되고 마지막 저장 시간부터 1초 이상 경과한 경우 사진을 저장
    if motion_detected and time.time() - last_save_time > 1: #움직임 감지, 사진 저장 후 1초 경과 시 True
        blink_led() # LED 작동
        current_time = time.strftime("%Y%m%d_%H%M%S") 
        cv2.imwrite(f'detected_frame_{current_time}.jpg', frame) # 움직임 감지된 현재 프레임 사진 저장
        last_save_time = time.time() #마지막 사진 저장한 시간 현재 시간으로 갱신

    last_frame = frame # 마지막 프레임을 현재 프레임으로 업데이트

    if time.time() - timestamp > 1:  # base_frame 갱신 이후 1초 이상 경과한 경우에만 True
        base_frame = gray_frame  # 기준 프레임 현재 프레임 흑백 버전으로 갱신
        timestamp = time.time() # 기준 프레임 갱신한 시간 현재 시간으로 갱신
    
    cv2. imshow('detected feed', frame) # frame 영상 보여줌
    
    if cv2.waitKey(1) & 0xFF == ord('q'): # 키보드에서 q 버튼 눌르면 while 루프 종료
        break 

cap.release() #카메라 리소스 해제
cv2.destroyAllWindows() #OpenCV로 생성된 모든 윈도우 닫음
GPIO.cleanup() # GPIO 리소스 초기 상태로 되돌림

 

위 코드를 실행할 경우 'detected feed'라는 영상 공유 프로그램이 실행됩니다. 또한 base_frame에서 일정 민감도 이상 움직일 경우에 1초마다 사진이 저장되고 저장된 사진에는 움직임이 포착된 지점에 녹색 네모칸이 표현됩니다.

 

필요한 경우 코드를 수정하여 원하는대로 바꿀 수 있습니다.

반응형