히스토그램 역투영

개요

  • 히스토그램 역투영(Histogram Backprojection)은 ROI(관심영역)의 색상 분포를 확률밀도(또는 빈도)로 추정한 뒤, 영상의 각 픽셀이 그 분포에 얼마나 일치하는지를 매핑하여 확률 맵(또는 유사도 맵) 을 만드는 방법.

+full

동작 과정

  1. ROI 선택: 관심 객체 또는 영역 선택.
  2. 히스토그램 계산: ROI 영역의 색상 분포 (HSV 공간에서 Hue 채널 등) 계산.
  3. 정규화(Normalization): 히스토그램 값을 전체적으로 비교 가능하도록 정규화.
  4. 역투영(Backprojection): 전체 영상에서 각 픽셀이 ROI 히스토그램에서 얼마나 높은 빈도를 가지는 색상인지 매핑.
    • 결과는 확률 이미지 (밝을수록 ROI와 색상 분포가 비슷)
  5. (옵션) 후처리: 모폴로지 연산, 블러링 등을 통해 노이즈 제거 후 객체 추적/검출에 활용.

히스토그램 역투영 방법

1. ROI 선택 및 히스토그램 계산

  • 히스토그램 역투영 대상 영역에 대한 ROI(Region of Interset)를 설정함.
  • 원본 이미지와, ROI 영역의 히스토그램을 계산함.
  • HSV 색공간을 이용, H(색상), S(채도) 채널을 이용함. (색공간 참고)
    • 2D 히스토그램 두 변수(Hue, Saturation)의 결합 카운트/빈도를 저장한 행렬
SourceROI

+full

import cv2
import numpy as np
 
# 이미지 읽기
img = cv2.imread('./source_images/original1.jpg')
 
# HSV 색공간으로 변환
hsv_img = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
 
# ROI 설정 (관심 영역)
x, y, w, h = 120, 300, 180, 180
hsv_roi = hsv_img[y:y+h, x:x+w]
 
# 히스토그램 계산 (H, S 채널 사용)
channels = [0, 1] # H=0, S=1
ranges = [0, 180, 0, 256] # H: [0,180), S: [0,256)
histSize = [180, 256] # H, S 각각 bin 개수
img_hist = cv2.calcHist([hsv_img], channels, None, histSize, ranges)
roi_hist = cv2.calcHist([hsv_roi], channels, None, histSize, ranges)
 
# 히스토그램 정규화 (0~255)
cv2.normalize(img_hist, img_hist, 0, 255, cv2.NORM_MINMAX)
cv2.normalize(roi_hist, roi_hist, 0, 255, cv2.NORM_MINMAX)

2. 확률 맵 생성

  • 원본 이미지와 ROI의 히스토그램 비율로 픽셀별 확률 맵 생성.
    • ROI/원본 이미지 값이 크면, 그 색(H, S)이 ROI일 가능성이 높음.
SourceBackprojection Map
# 히스토그램 비율 R = M / I 계산
R = roi_hist / (img_hist + 1) # 분모가 0이 되지 않도록 +1
 
# ROI HSV 채널 분리
h, s, v = cv2.split(hsv_img)
# Backprojection 계산
# h 값과 s 값에 해당하는 R 값을 추출
B = R[h.flatten(), s.flatten()] # 1D 배열
  
# 값이 1보다 크면 1로 제한
B = np.minimum(B, 1)
# 이미지 크기로 reshape
B = B.reshape(hsv_img.shape[:2])
# 0~255 정규화
B = cv2.normalize(B, None, 0, 255, cv2.NORM_MINMAX)
B = B.astype(np.uint8)
  • OpenCV의 calcBackProject() 함수를 이용한 방법
B = cv2.calcBackProject([hsv_img], [0,1], roi_hist, [0,180,0,256], 1)

3. 영역 추출 및 후처리

  • Backprojection 결과는 노이즈가 있을 수 있음 → 필터링을 이용해 노이즈 제거 및 스무딩.
  • 이진화를 통해 Backprojection 결과에서 임계값을 넘는 영역에 대한 마스크 생성.
  • 원본 이미지에서 마스크에 해당하는 영역만 추출.
Filtered MaskResult
# 원형 커널로 컨볼루션 → 주변 픽셀 보정
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))
cv2.filter2D(B, -1, kernel, B)
 
# 임계값 적용 (마스크 생성)
_, mask = cv2.threshold(B, 50, 255, 0)
 
# 원본 영상에서 mask 영역 추출
result = cv2.bitwise_and(img, img, mask=mask)

참고