😏2차원 히스토그램과 역투영

2차원 히스토그램과 역투영(Back Projection)

2차원 히스토그램을 통해 색상 분포를 분석하고, 역투영을 활용하여 특정 물체나 배경을 분리하는 방법을 학습할 수 있습니다.

2차원 히스토그램 (2D Histogram)

개념 이해

1차원 히스토그램은 이미지 안에 각 픽셀 값이 몇 개씩 있는지를 표현합니다. 반면 2차원 히스토그램은 두 개의 축을 가지며, 각 축이 만나는 지점의 픽셀 개수를 표현합니다.

예를 들어, Blue와 Green 채널의 2차원 히스토그램을 만들면 각 Blue-Green 값 조합이 이미지에서 얼마나 많이 나타나는지를 시각화할 수 있습니다.

2차원 히스토그램 생성 예제

# 2D 히스토그램 생성 (histo_2d.py)
import cv2
import matplotlib.pylab as plt

# 컬러 스타일을 1.x 스타일로 사용
plt.style.use('classic')

img = cv2.imread('../img/mountain.jpg')

# Blue와 Green 채널의 2D 히스토그램
plt.subplot(131)
hist = cv2.calcHist([img], [0,1], None, [32,32], [0,256,0,256])
p = plt.imshow(hist)
plt.title('Blue and Green')
plt.colorbar(p)

# Green과 Red 채널의 2D 히스토그램
plt.subplot(132)
hist = cv2.calcHist([img], [1,2], None, [32,32], [0,256,0,256])
p = plt.imshow(hist)
plt.title('Green and Red')
plt.colorbar(p)

# Blue와 Red 채널의 2D 히스토그램
plt.subplot(133)
hist = cv2.calcHist([img], [0,2], None, [32,32], [0,256,0,256])
p = plt.imshow(hist)
plt.title('Blue and Red')
plt.colorbar(p)

plt.show()

cv2.calcHist() 함수 매개변수

cv2.calcHist() 매개변수 설명

매개변수

설명

[img]

입력 이미지 (리스트 형태)

[0,1]

사용할 채널 (0:Blue, 1:Green, 2:Red)

None

마스크 (전체 이미지 사용시 None)

[32,32]

히스토그램 빈(bin) 개수 (각 채널당)

[0,256,0,256]

픽셀 값 범위 (첫 번째, 두 번째 채널)


역투영 (Back Projection)

개념과 원리

역투영은 관심 영역(ROI)의 히스토그램과 유사한 히스토그램을 갖는 영역을 찾아내는 기법입니다. 이를 통해 이미지 내에서 특정 물체나 배경을 분리할 수 있습니다.

작동 원리:

  1. 관심 영역(ROI)을 선택하여 기준 히스토그램 생성

  2. 전체 이미지에서 ROI와 유사한 색상 분포를 가진 영역 검출

  3. 유사한 부분은 흰색, 다른 부분은 검은색으로 분리

장점과 한계

장점:

  • 알파 채널이나 크로마 키 없이도 복잡한 모양의 사물 분리 가능

  • 색상 기반의 효과적인 객체 추출

한계:

  • 관심 영역과 비슷한 색상을 가진 다른 물체가 있을 경우 성능 저하

  • 조명 변화에 민감할 수 있음

역투영 구현 예제

# 마우스로 선택한 영역의 물체 분리하기 (histo_backproject.py)
import cv2
import numpy as np
import matplotlib.pyplot as plt

win_name = 'back_projection'
img = cv2.imread('../img/pump_horse.jpg')
hsv_img = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
draw = img.copy()

# 역투영된 결과를 마스킹해서 결과를 출력하는 공통함수
def masking(bp, win_name):
    disc = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5))
    cv2.filter2D(bp,-1,disc,bp)
    _, mask = cv2.threshold(bp, 1, 255, cv2.THRESH_BINARY)
    result = cv2.bitwise_and(img, img, mask=mask)
    cv2.imshow(win_name, result)

# 직접 구현한 역투영 함수
def backProject_manual(hist_roi):
    # 전체 영상에 대한 H,S 히스토그램 계산
    hist_img = cv2.calcHist([hsv_img], [0,1], None,[180,256], [0,180,0,256])
    
    # 선택영역과 전체 영상에 대한 히스토그램 비율 계산
    hist_rate = hist_roi / (hist_img + 1)
    
    # 비율에 맞는 픽셀 값 매핑
    h, s, v = cv2.split(hsv_img)
    bp = hist_rate[h.ravel(), s.ravel()]
    
    # 비율은 1을 넘어서는 안되므로 1을 넘는 수는 1로 설정
    bp = np.minimum(bp, 1)
    
    # 1차원 배열을 원래의 shape로 변환
    bp = bp.reshape(hsv_img.shape[:2])
    cv2.normalize(bp, bp, 0, 255, cv2.NORM_MINMAX)
    bp = bp.astype(np.uint8)
    
    # 역투영 결과로 마스킹해서 결과 출력
    masking(bp, 'result_manual')

# OpenCV API로 구현한 함수
def backProject_cv(hist_roi):
    # 역투영 함수 호출
    bp = cv2.calcBackProject([hsv_img], [0, 1], hist_roi, [0, 180, 0, 256], 1)
    # 역투영 결과로 마스킹해서 결과 출력
    masking(bp, 'result_cv')

# ROI 선택
(x, y, w, h) = cv2.selectROI(win_name, img, False)
if w > 0 and h > 0:
    roi = draw[y:y+h, x:x+w]
    # 빨간 사각형으로 ROI 영역 표시
    cv2.rectangle(draw, (x, y), (x+w, y+h), (0,0,255), 2)
    
    # 선택한 ROI를 HSV 컬러 스페이스로 변경
    hsv_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)
    
    # H,S 채널에 대한 히스토그램 계산
    hist_roi = cv2.calcHist([hsv_roi],[0, 1], None, [180, 256], [0, 180, 0, 256])
    
    # ROI의 히스토그램을 매뉴얼 구현함수와 OpenCV 함수에 각각 전달
    backProject_manual(hist_roi)
    backProject_cv(hist_roi)

cv2.imshow(win_name, draw)
cv2.waitKey()
cv2.destroyAllWindows()

역투영 알고리즘 상세 분석

수동 구현 방식 (backProject_manual)

핵심 개념: 히스토그램 비율을 이용한 픽셀 매핑

# 비율 계산
hist_rate = hist_roi / (hist_img + 1)

# 픽셀 값 매핑
bp = hist_rate[h.ravel(), s.ravel()]

이 코드에서 hist_rate[h.ravel(), s.ravel()]는 다음과 같이 작동합니다:

# 예시를 통한 이해
v = np.arange(6).reshape(2,3)
# v = [[0, 1, 2],
#      [3, 4, 5]]

row = np.array([1,1,1,0,0,0])
col = np.array([0,1,2,0,1,2])
result = v[row, col]  # [3, 4, 5, 0, 1, 2]
  • hist_rate는 히스토그램 비율을 값으로 가지는 2차원 배열

  • hs는 실제 이미지의 각 픽셀 값

  • 교차점의 비율을 해당 픽셀 값으로 매핑

OpenCV API 방식
cv2.calcBackProject(img, channel, hist, ranges, scale)
cv2.calcBackProject() 매개변수

매개변수

설명

img

입력 이미지, [img]처럼 리스트로 감싸서 사용

channel

처리할 채널, 리스트로 감싸서 사용

hist

역투영에 사용할 히스토그램

ranges

각 픽셀이 가질 수 있는 값의 범위

scale

결과에 적용할 배율 계수

후처리 과정 (masking 함수)

def masking(bp, win_name):
    # 타원형 구조 요소 생성 (표면 부드럽게 처리)
    disc = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5))
    cv2.filter2D(bp, -1, disc, bp)
    
    # 이진화 처리
    _, mask = cv2.threshold(bp, 1, 255, cv2.THRESH_BINARY)
    
    # 마스킹 적용하여 최종 결과 생성
    result = cv2.bitwise_and(img, img, mask=mask)
    cv2.imshow(win_name, result)

후처리 단계:

  1. 표면 평활화: cv2.filter2D()로 마스크 표면을 부드럽게 처리

  2. 이진화: 스레시홀딩을 통해 명확한 마스크 생성

  3. 마스킹: 원본 이미지에 마스크를 적용하여 최종 결과 출력

사용 방법

  1. 프로그램 실행 시 이미지 창에서 마우스로 관심 영역(ROI) 선택

  2. 선택 완료 후 엔터키 누름

  3. 선택된 영역과 유사한 색상을 가진 부분이 추출됨

  4. 수동 구현 결과OpenCV API 결과를 비교할 수 있음


실용적 활용 방안

적용 분야

  • 객체 추적: 동영상에서 특정 색상의 객체 추적

  • 배경 분리: 크로마키 없이 배경과 전경 분리

  • 색상 기반 분할: 유사한 색상 영역끼리 그룹화

  • 품질 검사: 제품의 색상 일관성 검사

성능 향상 팁

  • HSV 색공간 사용: RGB보다 조명 변화에 강함

  • 적절한 빈(bin) 크기: 너무 세분화하면 노이즈 증가

  • 후처리 적용: 모폴로지 연산으로 결과 품질 향상

  • ROI 선택 주의: 대표적인 색상 영역을 포함하도록 선택


참고 자료