귀퉁이 서재

OpenCV - 12. 이미지 유사도 비교, 사람 얼굴과 해골 합성, 모션 감지 CCTV 본문

OpenCV

OpenCV - 12. 이미지 유사도 비교, 사람 얼굴과 해골 합성, 모션 감지 CCTV

Baek Kyun Shin 2020. 9. 24. 22:42

이번 포스팅에서는 여러 가지 실습을 해보겠습니다. 이번 포스팅 역시 '파이썬으로 만드는 OpenCV 프로젝트(이세우 저)'를 정리한 것임을 밝힙니다.

코드: github.com/BaekKyunShin/OpenCV_Project_Python/tree/master/04.img_processing

이미지 유사도 비교 실습

이전 포스팅에서 히스토그램은 이미지의 픽셀 값의 분포를 나타낸다고 배웠습니다. 픽셀 값의 분포가 서로 비슷하다면 유사한 이미지일 확률이 높고, 분포가 서로 다르다면 서로 다른 이미지일 확률이 높습니다. 이러한 사실을 이용하여 이미지의 유사도를 측정할 수 있습니다. 즉, 두 이미지의 히스토그램을 비교하면 되는 것입니다. OpenCV는 히스토그램을 비교하여 두 이미지가 얼마나 유사한지 판단해주는 함수를 제공합니다.

method 파라미터에는 아래와 같은 값들이 있습니다.
cv2.HISTCMP_CORREL: 상관관계 (1: 완전 일치, -1: 완전 불일치, 0: 무관계)
cv2.HISTCMP_CHISQR: 카이제곱 (0: 완전 일치, 무한대: 완전 불일치)
cv2.HISTCMP_INTERSECT: 교차 (1: 완전 일치, 0: 완전 불일치 - 1로 정규화한 경우)

아래 예제는 서로 다른 각도에서 찍은 로봇 태권 V 장난감 이미지와 코주부 박사 장난감 이미지를 비교하는 코드입니다.

# 히스토그램 비교 (histo_compare.py)

import cv2, numpy as np
import matplotlib.pylab as plt

img1 = cv2.imread('../img/taekwonv1.jpg')
img2 = cv2.imread('../img/taekwonv2.jpg')
img3 = cv2.imread('../img/taekwonv3.jpg')
img4 = cv2.imread('../img/dr_ochanomizu.jpg')

cv2.imshow('query', img1)
imgs = [img1, img2, img3, img4]
hists = []
for i, img in enumerate(imgs) :
    plt.subplot(1,len(imgs),i+1)
    plt.title('img%d'% (i+1))
    plt.axis('off') 
    plt.imshow(img[:,:,::-1])
    #---① 각 이미지를 HSV로 변환
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    #---② H,S 채널에 대한 히스토그램 계산
    hist = cv2.calcHist([hsv], [0,1], None, [180,256], [0,180,0, 256])
    #---③ 0~1로 정규화
    cv2.normalize(hist, hist, 0, 1, cv2.NORM_MINMAX)
    hists.append(hist)


query = hists[0]
methods = {'CORREL' :cv2.HISTCMP_CORREL, 'CHISQR':cv2.HISTCMP_CHISQR, 
           'INTERSECT':cv2.HISTCMP_INTERSECT,
           'BHATTACHARYYA':cv2.HISTCMP_BHATTACHARYYA}
for j, (name, flag) in enumerate(methods.items()):
    print('%-10s'%name, end='\t')
    for i, (hist, img) in enumerate(zip(hists, imgs)):
        #---④ 각 메서드에 따라 img1과 각 이미지의 히스토그램 비교
        ret = cv2.compareHist(query, hist, flag)
        if flag == cv2.HISTCMP_INTERSECT: #교차 분석인 경우 
            ret = ret/np.sum(query)        #비교대상으로 나누어 1로 정규화
        print("img%d:%7.2f"% (i+1 , ret), end='\t')
    print()
plt.show()

각 이미지를 HSV 형식으로 변환한 뒤 H, V에 대하여 2차원 히스토그램을 계산해 정규화했습니다. 이렇게 정규화한 이미지를 원본 이미지와 비교합니다. 특이하게, cv2.HISTCMP_INTERSECT인 경우 원본 히스토그램으로 나누어주면 정규화가 되어 결과를 판별하기가 쉬워집니다. method 알고리즘 별 결과는 다음과 같습니다.

원본 이미지(위 그림에서 맨 왼쪽 큰 이미지)는 img1과 완전 일치하며, img4와 가장 유사하지 않다는 것을 확인할 수 있습니다.

사람 얼굴과 해골 얼굴 합성 실습

연습용으로 아래 사람 얼굴과 해골 얼굴을 반반씩 합성해보겠습니다. 

아래와 같이 그냥 반반씩 합치면 경계선이 뚜렷해 어색합니다. 알파 값을 조절하며 자연스럽게 이미지 합성을 해보겠습니다.

# 사람 얼굴과 해골 합성하기 (workshop_two_face.py)

import cv2
import numpy as np

# 영상의 15%를 알파 블렌딩의 범위로 지정
alpha_width_rate = 15

# 합성할 두 영상 읽기
img_face = cv2.imread('../img/man_face.jpg')
img_skull = cv2.imread('../img/skull.jpg')

# 입력 영상과 같은 크기의 결과 영상 준비
img_comp = np.zeros_like(img_face)

# 연산에 필요한 좌표 계산
height, width = img_face.shape[:2]
middle = width//2                             # 영상의 중앙 좌표
alpha_width = width * alpha_width_rate // 100 # 알파 블렌딩 범위
start = middle - alpha_width//2               # 알파 블렌딩 시작 지점
step = 100/alpha_width                        # 알파 값 간격

# 입력 영상의 절반씩 복사해서 결과 영상에 합성
img_comp[:, :middle, : ] = img_face[:, :middle, :].copy()
img_comp[:, middle:, :] = img_skull[:, middle:, :].copy()
cv2.imshow('half', img_comp)

# 알파 값을 바꾸면서 알파 블렌딩 적용
for i in range(alpha_width+1 ):
    alpha = (100 - step * i) / 100  # 증감 간격에 따른 알파 값 (1~0)
    beta = 1 - alpha                # 베타 값 (0~1)
    # 알파 블렌딩 적용
    img_comp[:, start+i] = img_face[:, start+i] * \
                                alpha + img_skull[:, start+i] * beta
    print(i, alpha, beta)
    
cv2.imshow('half skull', img_comp)
cv2.waitKey()
cv2.destroyAllWindows()

자연스럽게 반반씩 합성이 되었습니다. 이 예제에서 핵심 코드는 img_comp[:, start+i] = img_face[:, start+i] * alpha + img_skull[:, start+i] * beta 부분입니다. 사람 얼굴 이미지의 반쪽과 해골 이미지의 반쪽을 알파 값을 조정하며 합성하는 부분이기 때문입니다.

움직임 감지 CCTV 만들기 실습

모션 감지를 하기 위해서는 어떤 알고리즘을 써야 할까요? 단순히 전후 영상의 차이를 구하면 안 됩니다. 고정되어 있는 물체라도 미세한 움직임은 있을 수 있기 때문입니다. 따라서 세 개의 프레임 a, b, c를 순차적으로 얻어서 a와 b의 차이 그리고 b와 c의 차이가 모두 발견되는 경우에 한해서 움직임이 있는 것으로 판단해야 합니다. 아래 코드는 이런 방식으로 움직임을 감지하는 카메라를 실행시킵니다.

# 모션 감지 CCTV (workshop_cctv_motion_sensor.py)

import cv2
import numpy as np

# 감도 설정(카메라 품질에 따라 조정 필요)
thresh = 25    # 달라진 픽셀 값 기준치 설정
max_diff = 5   # 달라진 픽셀 갯수 기준치 설정

# 카메라 캡션 장치 준비
a, b, c = None, None, None
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 480)      # 프레임 폭을 480으로 설정 
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 320)     # 프레임 높이를 320으로 설정

if cap.isOpened():
    ret, a = cap.read()         # a 프레임 읽기
    ret, b = cap.read()         # b 프레임 읽기

    while ret:
        ret, c = cap.read()     # c 프레임 읽기
        draw = c.copy()         # 출력 영상에 사용할 복제본
        if not ret:
            break
        
        # 3개의 영상을 그레이 스케일로 변경
        a_gray = cv2.cvtColor(a, cv2.COLOR_BGR2GRAY)
        b_gray = cv2.cvtColor(b, cv2.COLOR_BGR2GRAY)
        c_gray = cv2.cvtColor(c, cv2.COLOR_BGR2GRAY)

        # a-b, b-c 절대 값 차 구하기 
        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)

        # 두 차이에 대해서 AND 연산, 두 영상의 차이가 모두 발견된 경우
        diff = cv2.bitwise_and(diff1_t, diff2_t)

        # 열림 연산으로 노이즈 제거 ---①
        k = cv2.getStructuringElement(cv2.MORPH_CROSS, (3,3))
        diff = cv2.morphologyEx(diff, cv2.MORPH_OPEN, k)

        # 차이가 발생한 픽셀이 갯수 판단 후 사각형 그리기
        diff_cnt = cv2.countNonZero(diff)
        if diff_cnt > max_diff:
            nzero = np.nonzero(diff)  # 0이 아닌 픽셀의 좌표 얻기(y[...], x[...])
            cv2.rectangle(draw, (min(nzero[1]), min(nzero[0])), \
                                (max(nzero[1]), max(nzero[0])), (0,255,0), 2)
            cv2.putText(draw, "Motion Detected", (10,30), \
                                cv2.FONT_HERSHEY_DUPLEX, 0.5, (0,0,255))
        
        # 컬러 스케일 영상과 스레시홀드 영상을 통합해서 출력
        stacked = np.hstack((draw, cv2.cvtColor(diff, cv2.COLOR_GRAY2BGR)))
        cv2.imshow('motion sensor',stacked )

        # 다음 비교를 위해 영상 순서 정리
        a = b
        b = c
        
        if cv2.waitKey(1) & 0xFF == 27:
            break                

 

 

PC에 내장 카메라가 있는 경우 코드를 실행하면 두 영상이 뜰 겁니다. 하나는 내 모습을 보여주는 영상이고, 다른 하나는 움직임을 감지하는 영상입니다. 몸을 좌우로 움직이면 움직이는 부분을 감지하여 표시합니다. 

아래의 두 코드는 미세한 노이즈를 제거하는 기능을 합니다. 

k = cv2.getStructuringElement(cv2.MORPH_CROSS, (3,3))
diff = cv2.morphologyEx(diff, cv2.MORPH_OPEN, k)

 

이상으로 지금까지 배운 OpenCV 코드를 기반으로 3가지 실습을 해봤습니다.

Comments