4장. 채널 처리와 히스토그램 분석
cv2.calcHist 의 인자
택배 회사에 "이 지역에서 온 상자만, 무게를 10개 구간으로, 0~50kg 범위에서 세어 주세요"라고 맡기는 장면을 떠올려 봅시다. 무엇을 셀지, 어디서 온 것만 셀지, 몇 구간으로 셀지, 어느 범위를 셀지를 알려 줘야 합니다. cv2.calcHist 도 같은 다섯 가지를 받습니다.
그림의 다섯 상자가 cv2.calcHist 에 주는 다섯 인자입니다. 마스크를 뺀 네 가지는 대괄호 목록으로 감싸서 줍니다. 결과는 구간마다의 픽셀 수를 담은 배열 하나입니다. 여기서 상자 더미가 이미지, 상자의 종류가 채널, "이 지역에서 온 것만"이 마스크, 무게 구간의 개수가 bin 수, 0~50kg 이 범위입니다.
다섯 인자
| 순서 | 인자 | 흑백 그림의 보통 값 | 뜻 |
|---|---|---|---|
| 1 | images | [gray] | 셀 그림. 여러 장을 받을 수 있게 목록으로 준다 |
| 2 | channels | [0] | 몇 번 채널을 셀까. 흑백은 0번 하나뿐이다 |
| 3 | mask | None | 셀 곳만 흰색인 판. 전체를 세면 None |
| 4 | histSize | [256] | 구간(bin) 개수 |
| 5 | ranges | [0, 256] | 셀 값의 범위. 끝 값 256 은 들어가지 않으므로 0~255 를 센다 |
결과의 모양과 np.histogram 과의 차이
| 견줄 것 | cv2.calcHist | np.histogram |
|---|---|---|
| 결과 | 도수 하나 | 도수와 구간 경계 두 개 |
| 도수의 모양 | OpenCV 5.0 은 (256,) 한 줄, 4.x 는 (256, 1) 세로로 긴 배열 | (256,), 한 줄 |
| 도수의 자료형 | float32(소수) | int64(정수) |
| 마스크 | 셋째 인자로 준다 | 없다(미리 골라낸 값을 넣는다) |
| 속도 | 그림에 맞춰 만들어 빠르다 | 무엇이든 셀 수 있다 |
짧은 예시
import cv2
import numpy as np
# 밝기 0~255 를 고르게 담은 아주 작은 흑백 그림(세로 2, 가로 4)
tiny = np.array([[0, 30, 60, 90],
[120, 200, 230, 255]], dtype=np.uint8)
# 구간을 4개로: 0~63, 64~127, 128~191, 192~255
hist = cv2.calcHist([tiny], # images: 목록으로 감싼 그림
[0], # channels: 0번 채널
None, # mask: 전체를 센다
[4], # histSize: 구간 4개
[0, 256]) # ranges: 0 이상 256 미만
print(hist.shape) # 결과: (4,) → OpenCV 5.0 기준. 4.x 에서는 (4, 1)
print([int(v) for v in hist.ravel()]) # 결과: [3, 2, 0, 3]ravel() 은 여러 줄 배열을 한 줄로 펴 줍니다. ravel 은 "실타래를 풀다"라는 뜻입니다. OpenCV 4.x 에서는 결과가 (4, 1) 처럼 세로로 긴 모양이라, ravel() 로 펴 두면 어느 판에서나 같은 꼴로 쓸 수 있습니다. 0, 30, 60 이 첫 구간, 90, 120 이 둘째 구간, 200, 230, 255 가 넷째 구간에 들어가고 셋째 구간(128~191)은 비었습니다.
주의할 점
- 범위를
[0, 255]로 주면 255 인 칸이 세어지지 않습니다. 끝 값은 들어가지 않으므로[0, 256]으로 줍니다. - 결과는 소수(float32)입니다. 찍을 때는
int(...)로 바꿔야76800.0대신76800이 나옵니다. - 인터넷의 예제는 대부분 OpenCV 4.x 기준이라 결과를 (256, 1) 로 설명하고
hist[k, 0]처럼 꺼냅니다. 교안의 OpenCV 5.0 에서는 (256,) 이라 이 코드가 오류를 냅니다.hist.ravel()로 펴서 쓰면 두 판 모두에서 돌아갑니다.