영상의 이진화

  • 영상의 픽셀 값을 0(검정색) 또는 255(흰색)/1 로 만드는 연산
    • 배경(background) vs 객체(Object)
    • 관심 영역(ROI) vs 비관심 영역(Non-ROI)

이진화의 예시

  • 검정색으로 염색 되어있는 세포 찾기

  • 배경은 흰색, 글씨 부분은 검정색으로 만들기

  • 서로 다른 지문이 같은 사람의 것인지, 다른 사람의 것인지 판별하기

  • 배경과 객체를 분리하는 마스크 영상을 만들기

image

그레이스케일 영상의 이진화

  • 간단한 임계값(threshold) 연산을 이용

image

image

  • Threshold 값을 설정함으로써 원하는 값을 찾을 수 있음

    • T1으로 설정하면 세포의 검정색 염색된 부분만 찾기

    • T2로 설정하면 전체 세포 찾기

image

threshold 함수

double threshold(InputArray src, OutputArray dst, double thresh, double maxval, int type);
  • src : 입력 영상. 다채널, 8비트 또는 32비트 실수형
  • dst : 출력 영상. src와 동일 크기, 타입, 채널 수
  • thresh : 사용자 지정 임계값
  • maxval : THRESH_BINARY 또는 THRESH_BINARY_INV 방법 시 최댓값
  • type : 임계값에 의한 변환 함수 지정 또는 자동 임계값 설정 방법 지정
    • THRESH_BINARY : Threshold 값보다 작은 경우 0으로 설정, 큰 값은 255로 설정
    • THRESH_BINARY_INV : Threshold 값보다 작은 경우 255로 설정, 큰 값은 0으로 설정 (THRESH_BINARY 반전)
    • THRESH_TRUNCH : Threshold 값보다 큰 경우 Threshold 값으로 지정함
    • THRESH_TOZERO : Threshold 값보다 작은 경우에 대해서만 다 0으로 지정하고 나머지는 그대로 둠
    • THRESH_TOZERO_INV : Threshold 값보다 큰 경우에 대해서만 다 0으로 지정하고 나머지는 그대로 둠 (THRESH_TOZERO 반전)
    • THRESH_OTSU : Otsu(오츠) 알고리즘으로 임계값 설정(영상을 분석해서 Threshold 값을 자동으로 설정)
    • THRES_TRIANGLE : 삼각 알고리즘으로 임계값 설정(영상을 분석해서 Threshold 값을 자동으로 설정)

image

  • 반환값 : 사용된 임계값

threshold 예제 코드

#include <iostream>
#include "opencv2/opencv.hpp"

using namespace std;
using namespace cv;

int t_value = 128;
void on_trackbar_threshold(int, void*);
Mat src, dst;

int main(int argc, char* argv[])
{
	String filename = "neutrophils.png";

	if (argc > 1) {
		filename = argv[1];
	}

	src = imread(filename, IMREAD_GRAYSCALE);

	if (src.empty()) {
		cerr << "Image load failed!" << endl;
		return -1;
	}

	namedWindow("src");
	imshow("src", src);

	namedWindow("dst"); 
	createTrackbar("Threshold", "dst", &t_value, 255, on_trackbar_threshold);
	on_trackbar_threshold(0, 0); // Call the function to initialize

	waitKey();
}

void on_trackbar_threshold(int, void*)
{
	threshold(src, dst, t_value, 255, THRESH_BINARY);
	imshow("dst", dst);
}

  • TRHESH_BINARY

image

  • THRESH_BINARY_INV

image

임계값 자동 결정 방법

  • 영상의 히스토그램이 biomodal이고, 전경&배경 픽셀 분포가 비슷하면?

image

  • 영상의 히스토그램이 biomodal 이지만, 전경&배경 픽셀 분포가 크게 다르면?

image

Otsu 이진화 방법
  • 입력 영상이 배경과 객체 2개로 구성되어 있다고 가정할 때
  • 임의의 임계값 T에 의해 나눠지는 두 픽셀 분포 그룹의 분산이 최소가 되는 T를 선택
  • 일종의 최적화 알고리즘(optimization algorithm)
  • threshold() 함수의 type 인자에 TRHESH_OTSU를 지정

image

Otsu 예제 코드
#include <iostream>
#include "opencv2/opencv.hpp"

using namespace std;
using namespace cv;

int main(void)
{
	Mat src = imread("rice.png", IMREAD_GRAYSCALE);

	if (src.empty()) {
		cerr << "Image load failed!" << endl;
		return -1;
	}

	Mat dst;
	double th = threshold(src, dst, 0, 255, THRESH_BINARY | THRESH_OTSU); // THRESH_OTSU를 사용
	// 반환하는 이진화 임계값을 th로 받음
	// THRESH_BINARY | THRESH_OTSU 를 THRESH_OTSU으로 대체해도 됨
	
	cout << "Otsu threshold value is " << th << "." << endl;

	imshow("src", src);
	imshow("dst", dst);
	waitKey();
}
  • dst 영상의 결과를 보면 위에서는 이진화를 잘 수행하지만, 살짝 어둡게 표시된 아래에서는 이진화를 잘 수행하지 못함

image

  • rice.png를 임계값 자동 결정 방법인 Otsu 알고리즘을 사용해 반환할 때 임계값 : 131

image

전역 이진화의 문제점

  • 영상 전체에 대해 동일한 임계값을 사용하여 이진화를 수행하는 기법인 전역 이진화는 불균일한 조명 환경에 취약
    • 위에서 진행한 Otsu 이진화 방법의 결과를 살펴보면 위쪽은 이진화가 잘 수행되지만, 살짝 어두운 아래쪽은 이진화가 잘 수행되지 않음
    • 2번째 이미지 같은 경우 오른쪽 상단은 밝지만, 좌측 하단은 어두운데 이 상태에서 전역 이진화를 수행하게되면 3번, 4번 이미지 처럼 잘 이진화가 안되는 것을 확인할 수 있음

image

  • 균일하지 않은 조명의 영향을 해결하려면?
    • 불균일한 조명 성분을 보상한 후 전역 이진화를 수행하는 방법은 쉽지 않음
    • Surface Fitting이 쉽지 않음

image

지역 이진화

  • 픽셀 또는 영역마다 다른 임계값을 사용하여 이진화를 수행하는 기법
  • 보통 영상을 특정 크기 영역으로 분할하여 이진화를 수행하거나 또는 각 픽셀 근방에 윈도우를 설정하고 해당 윈도우에서 임계값을 결정하여 이진화를 수행
    • 영역 또는 윈도의 크기는?
    • 윈도우 형태는? Uniform? Gaussian?
    • 윈도우를 겹칠 것인가? Overlap? Non-Overlap?
    • 윈도우 안에 배경 또는 객체만 존재한다면?
      • 배경과 객체가 공존하는 형태로 지역 이진화를 실행 해야함

image

image

지역 이진화 예제

#include <iostream>
#include "opencv2/opencv.hpp"

using namespace std;
using namespace cv;

int main()
{
	Mat src = imread("rice.png", IMREAD_GRAYSCALE);

	if (src.empty()) {
		cerr << "Image load failed!" << endl;
		return -1;
	}

	Mat dst1;
	threshold(src, dst1, 0, 255, THRESH_BINARY | THRESH_OTSU); // 자동으로 threshold를 설정하여 전역 이진화

	int bw = src.cols / 4; 
	int bh = src.rows / 4; // 128 * 128 짜리 16 구역으로 나뉘어서 threshold 함수를 실행

	Mat dst2 = Mat::zeros(src.rows, src.cols, CV_8UC1);

	for (int y = 0; y < 4; y++) {
		for (int x = 0; x < 4; x++) {
			Mat src_ = src(Rect(x*bw, y*bh, bw, bh)); // 원본영상의 부분영상을 추출
			Mat dst_ = dst2(Rect(x*bw, y*bh, bw, bh)); // 참조로 결과영상을 받아옴
			threshold(src_, dst_, 0, 255, THRESH_BINARY | THRESH_OTSU); 
			// 원본영상의 부분영상 src_의 이진화 결과를 dst_에 저장 
			// dst_를 dst2 참조로 결과영상을 받아와서 threshold 함수를 dst_에 실행해도 dst2에 갱신됨 
		}
	}

	imshow("src", src);
	imshow("dst1", dst1);
	imshow("dst2", dst2);
	waitKey();
}
  • 전역 이진화와 지역 이진화를 비교 했을때 지역 이진화 결과가 더 잘 나오는것을 확인할 수 있음
    • 기존 전역 이진화의 아래부분이 이진화가 잘 안됐는데, 지역 이진화에서는 잘 된 것을 확인할 수 있음

image

  • 다음과 같은 지역 이진화 방법은 OpenCV에서는 지원하지 않지만 적응형 이진화 방법을 지원함

적응형 이진화

  • 윈도우가 오버랩되면서 한 픽셀씩 이동하면서 각각의 픽셀에 대해서 윈도우를 설정하는 형태
    • 윈도우를 이동시키면서 하나씩 이동하면서 값을 비교하는 것이 아니라 평균 영상의 픽셀을 따로 만들어놓고 비교하는 형태
  • 지역 이진화보다 연산량이 많음

OpenCV 적응형 이진화 함수

void adaptiveThreshold(InputArray src, OutputArray dst, double maxValue, int adaptiveMethod, int thresholdType, int blockSize, double C);
  • src : 입력 영상
  • dst : 출력 영상
  • maxvalue : 이진화에서 사용할 최댓값
  • adaptiveMethod : 블록 평균 계산 방식 지정
    • ADAPTIVE_THRESH_MEAN_C : 산술평균
    • ADAPTIVE_THRESH_GAUSSIAN_C : 가우시안 가중치 평균
  • thresholdType : THRESH_BINARY or THRESH_BINARY_INV
  • blockSize : 사용할 블록의 크기 (3 이상의 홀수)
  • C : 블록 내 평균값 또는 가중 평균값에서 뺄 값
    • T(x,y) = 평균값(x,y) - C

image

적응형 이진화 예제

#include <iostream>
#include "opencv2/opencv.hpp"

using namespace std;
using namespace cv;

int block_size = 51;
Mat src, dst;

void on_trackbar(int, void*)
{
	int bsize = block_size; 

	if ((bsize & 0x00000001) == 0) // 짝수인지 아닌지 판단. 맨 마지막 비트가 1인지 아닌지로 판별해 짝수 판별
		bsize--; // 짝수일 경우 bsize를 1 감소

	if (bsize < 3) 
		bsize = 3; // 블럭 사이즈 값이 3보다 작아질 경우 3이 되도록 설정

	adaptiveThreshold(src, dst, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, bsize, 5);
	// 평균값을 계산할때 가우시안 블러 형태를 사용하는 것이 결과가 좋음
	// 일반적인 이진화를 수행하고 위에서 지정한 블럭사이즈 bsize를 사용
	// 현재 블럭에서 구한 평균값에서 5를 뺀 값을 이용해서 이진화를 수행함
	
	imshow("dst", dst);
}

int main()
{
	src = imread("sudoku.jpg", IMREAD_GRAYSCALE);

	if (src.empty()) {
		cerr << "Image load failed!" << endl;
		return -1;
	}

	namedWindow("src");
	imshow("src", src);

	namedWindow("dst");
	createTrackbar("Block Size", "dst", &block_size, 201, on_trackbar);
	on_trackbar(0, 0); // Call the function to initialize

	waitKey();
}
  • 전역 이진화에서는 결과 영상의 좌측 하단의 이진화가 잘 수행되지 않았지만, 적응형 이진화의 결과에서는 잘 수행되는 것을 확인할 수 있음

  • 블럭 사이즈가 수도쿠 한칸 안에 있을 경우에는 이진화가 잘 안될 수 있는데 결과 영상에서 띄엄띄엄 보이는 노이즈가 보임

image

  • adaptiveThreshold(src, dst, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, bsize, 5);
    • 랜덤으로 흰색, 검정색 으로 설정되던 것이 중간에서 5를 뺌으로써 임계값 위치를 작게 함으로써, 임계값 뒤에 있는 값들은 흰색으로 설정할 수 있음

image

  • adaptiveThreshold(src, dst, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, bsize, 0);
    • c 값을 0으로 설정하면 다음과 같이 지저분한 값들이 나오는 것을 확인할 수 있음

image