본문 바로가기
Developer/OpenCV

[OpenCV] 움직임을 인식하는 영상 (motion detecting)

by Doony 2020. 8. 15.

이번 포스팅에서는 openCV를 이용해, 움직임을 인식하는 방법에 대해 알아보겠습니다. 본 글은 이세우님의 '파이썬으로 만드는 openCV 프로젝트' 책을 참고했습니다.
지난 포스팅에서 openCV 카메라가 한 프레임씩 찍어서 윈도우에 송출하는 방식이라고 말씀드렸는데요. 움직임을 인식하는 원리는 여기에서부터 출발합니다.


움직임 인식의 원리?

현재 불러온 프레임과, 이전 프레임 사이의 값을 비교합니다. 여기서 값이란 픽셀마다 가지고 있는 RGB, 또는 GRAY스케일의 값을 의미합니다. 즉, 픽셀마다의 차이값을 통해, 변했다면 움직임이 있는 것이고 변하지 않았다면 움직임이 없다고 판단하는 것입니다.


전체 윈도우 사이즈를 대상으로, 해당 차이값 어레이를 구하고, 어느 부분이 움직임이 생겼는지 구할 수 있습니다. 이 때, 움직임을 정의할 유의미한 픽셀 차이값을 thresh값으로 정의해야합니다. 또한, 해당 thresh 이상의 차이, 즉 움직임을 보인 픽셀의 갯수도 정의해야합니다.

  • 픽셀 차이값 thresh
  • thresh 이상의 픽셀 갯수

현재의 프레임과 기존의 프레임, 2개만 가지고 비교한다면?

위에서 설명드린대로, 현재와 기존 프레임 딱 2개의 프레임만 가지고 보면 어떻게될까요? 코드는 아래와 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import cv2
import numpy as np
 
thresh = 25
max_diff = 5
 
a, b = NoneNone
 
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 600)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 400)
 
if cap.isOpened():
    ret, a = cap.read()
 
    while ret:
        ret, b = cap.read()
        if not ret:
            break
 
        a_gray = cv2.cvtColor(a, cv2.COLOR_BGR2GRAY)
        b_gray = cv2.cvtColor(b, cv2.COLOR_BGR2GRAY)
 
        diff1 = cv2.absdiff(a_gray, b_gray)
        ret, diff_t = cv2.threshold(diff1, thresh, 255, cv2.THRESH_BINARY)
 
        k = cv2.getStructuringElement(cv2.MORPH_CROSS, (33))
        diff = cv2.morphologyEx(diff_t, cv2.MORPH_OPEN, k)
 
        diff_cnt = cv2.countNonZero(diff)
        if diff_cnt > max_diff:
            nzero = np.nonzero(diff)
            cv2.rectangle(b, (min(nzero[1]), min(nzero[0])),
                          (max(nzero[1]), max(nzero[0])), (02550), 2)
 
            '''
            rectangle: pt1, pt2 기준으로 사각형 프레임을 만들어줌.
            nzero: diff는 카메라 영상과 사이즈가 같으며, a, b프레임의 차이 어레이를 의미함.
            (min(nzero[1]), min(nzero[0]): diff에서 0이 아닌 값 중 행, 열이 가장 작은 포인트
            (max(nzero[1]), max(nzero[0]): diff에서 0이 아닌 값 중 행, 열이 가장 큰 포인트
            (0, 255, 0): 사각형을 그릴 색상 값
            2 : thickness
            '''
 
            cv2.putText(b, "Motion detected!!", (1030),
                                                 cv2.FONT_HERSHEY_DUPLEX, 0.5, (00255))
 
        cv2.imshow('motion', b)
 
        a = b
 
        if cv2.waitKey(1& 0xFF == 27:
            break
cs

이렇게되면.. 아래와 같이 제대로 움직임을 인식하지 못합니다. 화면 전체가 움직이고 있다고 표현을 하고 있는데요.

모든 픽셀에 노이즈가 껴있기 때문입니다. 그냥 카메라를 보더라도 매 순간순간의 차이는 항상 발생하고 있는데요.
이를 보완하기 위해, 프레임 2개를 사용하는 것이 아니라 3개를 사용해서 보도록 하겠습니다.


프레임 3개로 움직임 인식하기

즉, 순차적으로 프레임 a, b, c를 얻고, a와 b, 그리고 b와 c 간의 비교를 하고 bitwiseAnd를 이용하는 방식입니다.


코드는 위와 유사합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import cv2
import numpy as np
 
thresh = 25
max_diff = 5
 
a, b, c = NoneNoneNone
 
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 480)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 320)
 
if cap.isOpened():
    ret, a = cap.read()
    ret, b = cap.read()
    while ret:
        ret, c = cap.read()
        draw = c.copy()
        if not ret:
            break
 
        a_gray = cv2.cvtColor(a, cv2.COLOR_BGR2GRAY)
        b_gray = cv2.cvtColor(b, cv2.COLOR_BGR2GRAY)
        c_gray = cv2.cvtColor(c, cv2.COLOR_BGR2GRAY)
 
        diff1 = cv2.absdiff(a_gray, b_gray)
        diff2 = cv2.absdiff(b_gray, c_gray)
 
        ret, diff1_t = cv2.threshold(diff1, thresh, 255, cv2.THRESH_BINARY)
        ret, diff2_t = cv2.threshold(diff2, thresh, 255, cv2.THRESH_BINARY)
 
        diff = cv2.bitwise_and(diff1_t, diff2_t)
 
        k = cv2.getStructuringElement(cv2.MORPH_CROSS, (33))
        diff = cv2.morphologyEx(diff, cv2.MORPH_OPEN, k)
 
        diff_cnt = cv2.countNonZero(diff)
        if diff_cnt > max_diff:
            nzero = np.nonzero(diff)
            cv2.rectangle(draw, (min(nzero[1]), min(nzero[0])),
                          (max(nzero[1]), max(nzero[0])), (02550), 2)
 
            '''
            rectangle: pt1, pt2 기준으로 사각형 프레임을 만들어줌.
            nzero: diff는 카메라 영상과 사이즈가 같으며, a, b프레임의 차이 어레이를 의미함.
            (min(nzero[1]), min(nzero[0]): diff에서 0이 아닌 값 중 행, 열이 가장 작은 포인트
            (max(nzero[1]), max(nzero[0]): diff에서 0이 아닌 값 중 행, 열이 가장 큰 포인트
            (0, 255, 0): 사각형을 그릴 색상 값
            2 : thickness
            '''
 
            cv2.putText(draw, "Motion detected!!", (1030),
                        cv2.FONT_HERSHEY_DUPLEX, 0.5, (00255))
 
        stacked = np.hstack((draw, cv2.cvtColor(diff, cv2.COLOR_GRAY2BGR)))
        cv2.imshow('motion', stacked)
 
        a = b
        b = c
 
        if cv2.waitKey(1& 0xFF == 27:
            break
 
 
cs

이번에는 비교를 위해 bitwise_and로 차이점이 공존하는 영역이 표시되는 diff도 같이 출력해봤습니다.
아래 보시는 것처럼 보다 정확하게 움직임이 있는 영역을 캐치해내는 것을 볼 수 있습니다.


집안용 CCTV를 만들어보자

동일한 원리를 이용하면 간단하게 집안용 cctv를 만들어볼 수 있습니다. 아이를 키우고 있는데, 아이가 안보는 사이에 움직임이 있다거나 하는 걱정이 늘 있었는데요. 다음에 기회가 된다면 라즈베리파이와 카메라 모듈을 활용해서 이런 시스템을 만들어보겠습니다.

댓글