5.18. 히스토그램과 통계

이미지의 픽셀을 변경하는 연산들과 함께, Image 클래스는 픽셀을 측정하는 메서드 군도 갖추고 있습니다. 픽셀 값의 분포를 요약하고, 평균 및 중앙값 밝기를 반환하며, 어두운 픽셀과 밝은 픽셀 사이의 최적 분기점을 찾고, 색상 채널의 산포를 보고합니다. 이러한 측정값은 두 가지 방식으로 애플리케이션에 활용됩니다. 어떤 임계값을 사용할지, 어떤 게인을 설정할지, 장면의 색조 프로파일이 어떻게 보이는지 결정하는 코드의 입력으로 쓰이고, 또한 “장면이 충분히 밝은가?”와 같은 진단 신호로서 애플리케이션이 특정 픽셀에 관한 결정을 내리지 않고도 대응할 수 있게 해줍니다.

거의 모든 측정의 출발점은 히스토그램입니다.

5.18.1. 히스토그램

이미지의 히스토그램은 각각의 가능한 밝기 값을 가진 픽셀이 몇 개나 되는지 센 것입니다. 그레이스케일 이미지의 경우 0 부터 255 까지의 값으로 인덱싱된 카운트 목록입니다. 색상 이미지의 경우 채널당 하나씩 세 개의 그러한 목록이 됩니다.

get_histogram() 가 이를 계산합니다:

h = img.get_histogram()

반환된 객체는 histogram 결과로, 채널별 빈(bin) 목록과 그에 대한 몇 가지 상위 수준 질의를 노출합니다. 빈 카운트는 합이 1.0 이 되도록 정규화되어 있습니다. 즉 히스토그램은 절대 픽셀 수가 아니라 분포의 프로파일을 기술하며, 이는 서로 다른 크기의 이미지들 간에 측정값을 비교 가능하게 만들어 줍니다.

그레이스케일 이미지의 경우 히스토그램은 빈 채널이 하나이며 h.bins() (또는 동등하게 h[0])로 사용할 수 있습니다. RGB565 이미지의 경우 히스토그램은 이진 임계값 처리 페이지에서 소개한 LAB 색 공간에서 계산되며, 세 개의 빈 채널 h.l_bins(), h.a_bins(), h.b_bins() (또는 h[0], h[1], h[2])로 사용할 수 있습니다. LAB는 임계값 및 추적 메서드가 사용하는 것과 동일한 선택입니다. 히스토그램은 색상이 어떤 공간에서 측정되는지에 대해 임계값과 일치합니다.

5.18.2. 빈과 빈 개수

기본 히스토그램은 가능한 픽셀 값마다 하나의 빈을 가집니다. 즉 8비트 채널의 경우 256 개의 빈입니다. 때로는 이것이 애플리케이션이 필요로 하는 것보다 더 세밀한 해상도일 수 있습니다. 분포의 대략적인 프로파일에만 관심이 있는 분류기는 32 개 또는 심지어 8 개와 같이 더 적은 빈 개수로 더 잘 동작할 수 있는데, 이는 더 빠르게 실행되고 노이즈에 대해 더 깔끔한 결과를 만들어 냅니다. bins 키워드(그리고 색상의 경우 채널별 l_bins, a_bins, b_bins)가 그 개수를 설정합니다:

h = img.get_histogram(bins=32)

ROI 및 임계값 범위 지정은 다른 모든 측정 메서드와 동일한 방식으로 동작합니다. roi 를 전달하여 히스토그램을 픽셀의 직사각형 영역으로 제한하고, thresholds 목록을 전달하여 해당 범위에 일치하는 픽셀만 포함시킵니다. 임계값 형태는 “일치하는 픽셀만의 히스토그램을 계산하기”를 단일 호출 연산으로 만들어 주는데, 이는 애플리케이션이 이미 검출된 영역의 질감을 픽셀을 직접 순회하지 않고도 특성화하고자 할 때 흔히 쓰이는 패턴입니다.

밝기 범위 0에서 255에 걸쳐 막대들의 행으로 그려진 그레이스케일 히스토그램. 분포에는 두 개의 봉우리가 있는데, 더 작은 어두운 봉우리와 더 큰 밝은 봉우리가 명확한 골짜기로 분리되어 있습니다. 세 개의 수직선이 겹쳐져 있습니다. 골짜기에 있는 Otsu 임계값, 더 큰 밝은 봉우리 쪽으로 치우친 평균, 그리고 누적 픽셀 수가 절반에 도달하는 더 오른쪽의 중앙값입니다.

세 가지 요약 측정값이 겹쳐진 그레이스케일 히스토그램: Otsu의 임계값(어두운 클러스터와 밝은 클러스터를 가장 잘 분리하는 분기점), 평균, 그리고 중앙값. 각 측정값은 동일한 분포에 대해 서로 다른 것을 말해 줍니다.

5.18.3. 통계

히스토그램은 모든 값의 출현 빈도에 대한 기술입니다. 통계는 그로부터 도출된 수치 요약입니다. get_statistics() 가 반환하는 statistics 객체는 표준 집합을 담고 있습니다:

  • mean – 픽셀 값의 산술 평균.

  • median – 픽셀의 절반이 그 아래에 위치하는 값.

  • mode – 가장 흔한 단일 값.

  • stdev – 표준 편차, 평균 주위의 산포를 나타내는 척도.

  • minmax – 존재하는 가장 밝은 픽셀 값과 가장 어두운 픽셀 값.

  • lquq – 하위 사분위수 및 상위 사분위수 분기점.

RGB565 이미지의 경우 채널별 형태(l_mean, a_median, b_mode 등)가 동일한 측정값을 채널별로 제공합니다.

이러한 수치들의 대부분은 특정 맥락에서 등장합니다. meanstdev 는 함께 노이즈 추정값을 제공합니다. 균일해야 하는 장면은 작은 stdev를 가지는 반면, 노이즈가 많은 센서는 같은 장면에 더 큰 stdev를 부여합니다. minmax 는 이미지의 대비를 제공합니다. 둘이 가까울수록 장면은 더 평탄하고, 멀수록 알고리즘이 다룰 수 있는 다이내믹 레인지가 더 큽니다. median 은 분포에 이상치가 있을 때의 강건한 중심입니다(매우 밝은 몇몇 픽셀이 평균을 끌어당기는 것처럼 중앙값을 끌어당기지는 않습니다). mode 는 가장 흔한 단일 값으로, 배경이 대부분의 픽셀을 덮고 있는 이미지의 배경 수준을 찾는 데 유용합니다.

get_statistics() 는 내부적으로 히스토그램 패스를 실행한 다음 이를 요약합니다. 이전에 계산한 히스토그램과 동일한 thresholdsroi 인수를 전달하면 동일한 픽셀 집합에 대한 통계를 생성합니다.

5.18.4. 백분위수와 CDF 조회

histogram 객체는 분수를 픽셀 값으로 변환하는 get_percentile() 메서드를 노출합니다. 즉 요청한 비율의 픽셀이 그 아래에 위치하는 값을 반환합니다. h.get_percentile(0.5) 는 중앙값이고, h.get_percentile(0.05)h.get_percentile(0.95) 는 함께 하위 및 상위 5%를 이상치로 무시하는 강건한 최솟값/최댓값을 제공합니다.

이는 애플리케이션이 소수의 떠도는 밝거나 어두운 픽셀이 답을 왜곡하게 두지 않으면서 픽셀 값의 범위를 특성화하고자 할 때 사용하는 형태입니다. 5번째 및 95번째 백분위수로부터 얻은 강건한 최솟값/최댓값은 또한 대비 확장(contrast-stretching) 패스, 즉 색조 보정에서 다루는 픽셀별 재매핑의 자연스러운 입력이기도 합니다.

5.18.5. Otsu의 방법

히스토그램은 그 자체로 짚고 넘어갈 만한 또 하나의 질문에 답합니다. 픽셀이 “어두운” 클러스터와 “밝은” 클러스터로 나뉘는 이미지가 주어졌을 때, 둘 사이의 분기점은 무엇인가? 임계값 처리 페이지에서는 이미 그 메커니즘을 결과로 명명했지만 – 애플리케이션이 binary() 에 넘겨줄 수 있는 단일 전역 임계값 – 방법은 미뤄두었습니다. 그 방법이 바로 Otsu의 방법이며, 히스토그램에 있습니다.

직관은 이렇습니다. 명확한 전경과 배경을 가진 이미지는 밝기 히스토그램에 두 개의 클러스터를 가지며, 그 사이에 골짜기가 있습니다. 임계값을 두기에 올바른 곳은 골짜기의 바닥, 즉 두 클러스터가 가장 잘 분리되는 값입니다. Otsu의 방법은 가능한 모든 분기점을 탐색하여 클러스터 내 분산이 가장 작은 곳(이는 클러스터 간 분산이 가장 크다는 말과 같습니다)을 선택하며, 그 결과는 해당 특정 이미지 분포에 대한 최적의 이진 분할입니다.

histogram 객체는 get_threshold 를 통해 Otsu를 노출합니다:

h = img.get_histogram()
t = h.get_threshold()

반환된 threshold 객체는 선택된 분기점을 담는 value (그레이스케일의 경우) 또는 l_value / a_value / b_value (색상의 경우) 속성을 가집니다. 그 결과를 곧바로 binary() 에 다시 넣으면 분기점을 이미지 자체가 선택하는 자기 조정 전역 임계값을 얻을 수 있습니다:

img.binary([(t.value, 255)])

이 패턴은 필터 기반 적응형 임계값이 해결하는 불균일 조명 문제를 해결하지는 않습니다. 이 패턴이 해결하는 것은 전역 임계값 처리가 이미 올바른 접근법일 때 “어떤 값에서 잘라야 하는가?”라는 질문입니다. 전경/배경 구분이 잘 정의된 장면의 경우, Otsu가 선택하는 값은 보통 사람이 눈으로 선택할 값과 몇 단위 이내로 일치합니다.

5.18.6. 차분 이미지에 대한 계산

get_histogram()get_statistics() 에 관한 유용한 세부 사항이 있습니다. 둘 다 다른 Image 를 받는 difference 키워드를 받아들이며, 별도의 차분 이미지를 할당하지 않고 소스와 그 이미지 사이의 픽셀별 차이의 히스토그램(또는 통계)을 계산합니다. 이는 측정만이 유일한 목적인 이미지를 생성하기 위해 명시적인 difference() 호출 비용을 치르지 않고도 “기준 프레임 이후로 장면이 얼마나 변했는가?”를 묻는 저렴한 방법입니다. 지속적으로 실행되는 모션 검출 스크립트의 경우 이러한 절약이 쌓입니다.