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() 함수 매개변수
|
매개변수 |
설명 |
|---|---|
|
[img] |
입력 이미지 (리스트 형태) |
|
[0,1] |
사용할 채널 (0:Blue, 1:Green, 2:Red) |
|
None |
마스크 (전체 이미지 사용시 None) |
|
[32,32] |
히스토그램 빈(bin) 개수 (각 채널당) |
|
[0,256,0,256] |
픽셀 값 범위 (첫 번째, 두 번째 채널) |
역투영 (Back Projection)
개념과 원리
역투영은 관심 영역(ROI)의 히스토그램과 유사한 히스토그램을 갖는 영역을 찾아내는 기법입니다. 이를 통해 이미지 내에서 특정 물체나 배경을 분리할 수 있습니다.
작동 원리:
-
관심 영역(ROI)을 선택하여 기준 히스토그램 생성
-
전체 이미지에서 ROI와 유사한 색상 분포를 가진 영역 검출
-
유사한 부분은 흰색, 다른 부분은 검은색으로 분리
장점과 한계
장점:
-
알파 채널이나 크로마 키 없이도 복잡한 모양의 사물 분리 가능
-
색상 기반의 효과적인 객체 추출
한계:
-
관심 영역과 비슷한 색상을 가진 다른 물체가 있을 경우 성능 저하
-
조명 변화에 민감할 수 있음
역투영 구현 예제
# 마우스로 선택한 영역의 물체 분리하기 (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차원 배열 -
h와s는 실제 이미지의 각 픽셀 값 -
교차점의 비율을 해당 픽셀 값으로 매핑
OpenCV API 방식
cv2.calcBackProject(img, channel, hist, ranges, scale)
|
매개변수 |
설명 |
|---|---|
|
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)
후처리 단계:
-
표면 평활화:
cv2.filter2D()로 마스크 표면을 부드럽게 처리 -
이진화: 스레시홀딩을 통해 명확한 마스크 생성
-
마스킹: 원본 이미지에 마스크를 적용하여 최종 결과 출력
사용 방법
-
프로그램 실행 시 이미지 창에서 마우스로 관심 영역(ROI) 선택
-
선택 완료 후 엔터키 누름
-
선택된 영역과 유사한 색상을 가진 부분이 추출됨
-
수동 구현 결과와 OpenCV API 결과를 비교할 수 있음
실용적 활용 방안
적용 분야
-
객체 추적: 동영상에서 특정 색상의 객체 추적
-
배경 분리: 크로마키 없이 배경과 전경 분리
-
색상 기반 분할: 유사한 색상 영역끼리 그룹화
-
품질 검사: 제품의 색상 일관성 검사
성능 향상 팁
-
HSV 색공간 사용: RGB보다 조명 변화에 강함
-
적절한 빈(bin) 크기: 너무 세분화하면 노이즈 증가
-
후처리 적용: 모폴로지 연산으로 결과 품질 향상
-
ROI 선택 주의: 대표적인 색상 영역을 포함하도록 선택