[2023-04-27] 1. 영상의 이진화, 지역 이진화
영상의 이진화
- 영상의 픽셀 값을 0(검정색) 또는 255(흰색)/1 로 만드는 연산
- 배경(background) vs 객체(Object)
- 관심 영역(ROI) vs 비관심 영역(Non-ROI)
이진화의 예시
-
검정색으로 염색 되어있는 세포 찾기
-
배경은 흰색, 글씨 부분은 검정색으로 만들기
-
서로 다른 지문이 같은 사람의 것인지, 다른 사람의 것인지 판별하기
-
배경과 객체를 분리하는 마스크 영상을 만들기
그레이스케일 영상의 이진화
- 간단한 임계값(threshold) 연산을 이용
-
Threshold 값을 설정함으로써 원하는 값을 찾을 수 있음
-
T1으로 설정하면 세포의 검정색 염색된 부분만 찾기
-
T2로 설정하면 전체 세포 찾기
-
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 값을 자동으로 설정)
- 반환값 : 사용된 임계값
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
- THRESH_BINARY_INV
임계값 자동 결정 방법
- 영상의 히스토그램이 biomodal이고, 전경&배경 픽셀 분포가 비슷하면?
- 영상의 히스토그램이 biomodal 이지만, 전경&배경 픽셀 분포가 크게 다르면?
Otsu 이진화 방법
- 입력 영상이 배경과 객체 2개로 구성되어 있다고 가정할 때
- 임의의 임계값 T에 의해 나눠지는 두 픽셀 분포 그룹의 분산이 최소가 되는 T를 선택
- 일종의 최적화 알고리즘(optimization algorithm)
- threshold() 함수의 type 인자에 TRHESH_OTSU를 지정
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 영상의 결과를 보면 위에서는 이진화를 잘 수행하지만, 살짝 어둡게 표시된 아래에서는 이진화를 잘 수행하지 못함
- rice.png를 임계값 자동 결정 방법인 Otsu 알고리즘을 사용해 반환할 때 임계값 : 131
전역 이진화의 문제점
- 영상 전체에 대해 동일한 임계값을 사용하여 이진화를 수행하는 기법인 전역 이진화는 불균일한 조명 환경에 취약
- 위에서 진행한 Otsu 이진화 방법의 결과를 살펴보면 위쪽은 이진화가 잘 수행되지만, 살짝 어두운 아래쪽은 이진화가 잘 수행되지 않음
- 2번째 이미지 같은 경우 오른쪽 상단은 밝지만, 좌측 하단은 어두운데 이 상태에서 전역 이진화를 수행하게되면 3번, 4번 이미지 처럼 잘 이진화가 안되는 것을 확인할 수 있음
- 균일하지 않은 조명의 영향을 해결하려면?
- 불균일한 조명 성분을 보상한 후 전역 이진화를 수행하는 방법은 쉽지 않음
- Surface Fitting이 쉽지 않음
지역 이진화
- 픽셀 또는 영역마다 다른 임계값을 사용하여 이진화를 수행하는 기법
- 보통 영상을 특정 크기 영역으로 분할하여 이진화를 수행하거나 또는 각 픽셀 근방에 윈도우를 설정하고 해당 윈도우에서 임계값을 결정하여 이진화를 수행
- 영역 또는 윈도의 크기는?
- 윈도우 형태는? Uniform? Gaussian?
- 윈도우를 겹칠 것인가? Overlap? Non-Overlap?
- 윈도우 안에 배경 또는 객체만 존재한다면?
- 배경과 객체가 공존하는 형태로 지역 이진화를 실행 해야함
지역 이진화 예제
#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();
}
- 전역 이진화와 지역 이진화를 비교 했을때 지역 이진화 결과가 더 잘 나오는것을 확인할 수 있음
- 기존 전역 이진화의 아래부분이 이진화가 잘 안됐는데, 지역 이진화에서는 잘 된 것을 확인할 수 있음
- 다음과 같은 지역 이진화 방법은 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
적응형 이진화 예제
#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();
}
-
전역 이진화에서는 결과 영상의 좌측 하단의 이진화가 잘 수행되지 않았지만, 적응형 이진화의 결과에서는 잘 수행되는 것을 확인할 수 있음
-
블럭 사이즈가 수도쿠 한칸 안에 있을 경우에는 이진화가 잘 안될 수 있는데 결과 영상에서 띄엄띄엄 보이는 노이즈가 보임
adaptiveThreshold(src, dst, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, bsize, 5);
- 랜덤으로 흰색, 검정색 으로 설정되던 것이 중간에서 5를 뺌으로써 임계값 위치를 작게 함으로써, 임계값 뒤에 있는 값들은 흰색으로 설정할 수 있음
adaptiveThreshold(src, dst, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, bsize, 0);
- c 값을 0으로 설정하면 다음과 같이 지저분한 값들이 나오는 것을 확인할 수 있음