[OpenCV] 04-10. Histograms in OpenCV (4)
🐍Python/OpenCV

[OpenCV] 04-10. Histograms in OpenCV (4)

728x90
반응형

< Histogram BackProjection >

이번 장에서는 “히스토그램 배경투사”에 대해서 알아볼 것 이다.

Theory

이는 Michael J. Swain , Dana H. Ballard의 논문 Indexing via color histograms에서 등장했다.

간단하게 뭐라고 말할 수 있을까? 이는 이미지 분할 또는 이미지에서 관심 있는 객체를 찾는데 사용된다. 간단히 말해서, 입력과 동일한 크기의 이미지를 생성하는데, 여기서 각각의 픽셀은 우리의 물체에 속하는 픽셀의 확률에 해당한다. 좀 더 단순한 말로, 출력 이미지는 관심있는 객체부분은 나머지 부분에 비해 하얗게 표시될 것 이다. 이는 직관적인 설명이다. 히스토그램 배경투사는 camshift 알고리즘등과 함께 사용된다.

어떻게 해야 할까? 관심 대상을 포함하는 이미지의 히스토그램을 만든다. 객체는 더 나은 결과를 위해 가능한 한 이미지를 채워야 한다. 그리고 컬러 히스토그램은 흑백스케일 히스토그램보다 선호된다. 왜냐하면 객체의 색상이 흑백스케일 강도보다 물체를 정의하는 더 나은 방법이기 때문이다. 그런 다음 우리는 객체를 찾아야하는 테스트 이미지 위에 이 히스토그램을 “배경투사”한다. 즉, 표면에 속하는 모든 픽셀의 확룰을 계산하여 보여주는 것이다. 적절한 임계값에 대한 결과 산출물은 단독으로 표면을 제공한다.

Algorithm in Numpy

(1) 먼저 우리가 찾아야 할 대상(M)의 컬러 히스토그램과 우리가 검색할 이미지(I)를 모두 계산해야 한다.

import cv2
import numpy as np
import matplotlib.pyplot as plt

# ROI는 객체이거나 우리가 찾아야 하는 객체의 영역이다.
roi = cv2.imread('./images/golden.jpg')
hsv = cv2.cvtColor(roi,cv2.COLOR_BGR2HSV)

# 목표는 우리가 찾을 이미지이다. 
target = cv2.imread('./images/golden.jpg')
hsvt = cv2.cvtColor(target,cv2.COLOR_BGR2HSV)

# calcHist를 사용하여 히스토그램을 찾는다. np.histogram2d도 가능하다
M = cv2.calcHist([hsv],[0, 1], None, [180, 256], [0, 180, 0, 256] )
I = cv2.calcHist([hsvt],[0, 1], None, [180, 256], [0, 180, 0, 256] )

(2) 비율 R=MI 을 찾는다. 그리고 R을 투사시키는 것, R을 팔레트로 사용하여 모든 픽셀을 대상으로 새 이미지를 생성하는 것이다. 즉, B(x,y)=R[h(x,y),s(x,y)] h는 색조이고 s는 (x,y)에서 픽셀의 채도이다. 이후에 다음 조건을 적용한다. B(x,y)=min[B(x,y),1]

h,s,v = cv2.split(hsvt)
R = M/I
B = R[h.ravel(), s.ravel()]
B = np.minimum(B,1)
B = B.reshape(hsvt.shape[:2])

(3) 이제 원형 convolution을 적용한다. B = D*B, D는 원형 커널이다.
(4) 가장 밝은 곳의 위치는 객체의 위치를 알려준다. 이미지의 한 영역이 예상되는 경우 적절한 값에 대한 임계값을 지정하면 좋은 결과를 얻을 수 있다.

그러면 원하는 대상, 원치 않는 대상만을 추출할 수 있다.

Bacprojection in OpenCV

OpenCV는 기존의 함수인 cv2.calcBackProject()를 제공한다. 이 함수의 파라미터는 cv2,calcHist()함수와 거의 유사하다. 파라미터 중에 하나는 객체의 히스토그램인 히스토그램이고, 그것을 찾아야 한다. 또한, 객체 히스토그램은 투사함수에 보내지기 전에 정규화되어야 한다. 이는 확률 이미지를 반환하기 때문이다. 그리고 원형 convolution 연산을 하고 임계값에 적용한다. 아래 코드와 결과를 보자.

코드 출처!

import cv2
import numpy as np

ix, iy = -1, -1
mode = False
img1, img2 = None,None

def onMouse(event,x,y,flag,param):
    global ix,iy,mode, img1,img2

    if event == cv2.EVENT_LBUTTONDOWN:
        mode = True
        ix,iy = x,y

    elif event == cv2.EVENT_MOUSEMOVE:
        if mode:
            img1 = img2.copy()
            cv2.rectangle(img1,(ix,iy),(x,y),(0,0,255),2)
            cv2.imshow('original',img1)

    elif event == cv2.EVENT_LBUTTONUP:
        mode = False
        if ix >= x or iy >= y:
            return

        cv2.rectangle(img1,(ix,iy),(x,y),(0,0,255),2)
        roi = img1[iy:y,ix:x]
        backProjection(img2,roi)

    return

def backProjection(img,roi):
    hsv = cv2.cvtColor(roi,cv2.COLOR_BGR2HSV)
    hsvt = cv2.cvtColor(img,cv2.COLOR_BGR2HSV)

    roihist = cv2.calcHist([hsv],[0,1],None,[180,256],[0,180,0,256])
    cv2.normalize(roihist,roihist,0,255,cv2.NORM_MINMAX)
    dst = cv2.calcBackProject([hsvt],[0,1],roihist,[0,180,0,256],1)

    disc = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5))
    cv2.filter2D(dst,-1,disc,dst)

    ret, thr = cv2.threshold(dst,50,255,0)
    thr = cv2.merge((thr,thr,thr))
    res = cv2.bitwise_and(img,thr)

    cv2.imshow('backproj',res)

def main():
    global img1, img2

    img1 = cv2.imread('images/golden.jpg')
    img2 = img1.copy()

    cv2.namedWindow('original'), cv2.namedWindow('backproj')
    cv2.setMouseCallback('original', onMouse,param=None)

    cv2.imshow('backproj',img2)

    while True:
        cv2.imshow('original', img1)

        k = cv2.waitKey(1) & 0xFF
        if k == 27:
            break

    cv2.destroyAllWindows()

main()



선택을 해서 원하는 부분을 얻을 수도 필요하지 않은 부분을 누락시킬 수 있다. 


누끼 따는 거랑 살짝 비슷한듯...?



728x90
반응형