5.1 영상의 밝기 조절
5.1.1 그레이스케일 영상 다루기
* 앞으로 나오는 대부분의 영상처리 알고리즘은 컬러 영상이 아닌 그레이스케일 영상을 대상으로 함
OpenCV에서 영상 파일을 그레이스케일 형태로 불러오기 위해서는 imread() 함수의 두 번째 인자에
IMREAD_GRAYSCALE 플래그를 설정해야 한다.
- lenna.bmp 파일로부터 레나 영상을 그레이스케일 영상 형태로 불러오기
Mat img1 = imread("lenna.bmp", IMREAD_GRAYSCALE); - 프로그램 동작 중 그레이스케일 영상을 저장할 새로운 Mat 객체를 생성 (모든 픽셀값 0으로 초기화, 640x480)
Mat img2(480, 640, CV_8UC1, Scalar(0)); - 이미 3채널 컬러 영상을 가지고 있고, 해당 영상을 그레이스케일 영상으로 변환
Mat img3 = imread("lenna.bmp", CV_8UC1, Scalar(0));
Mat img4;
cvtColor(img3, img4, COLOR_BGR2GRAY);
5.1.2 영상의 밝기 조절
*영상 밝기 조절 방법: 영상의 모든 픽셀에 일정 값을 더하거나 빼는 작업을 수행.
양수 값을 더하면 영상이 밝아지고, 반대로 양수 값을 빼면 영상이 어두워짐
수식: dst(x, y) = src(x, y) + n
scr = 입력 영상
dst = 출력 영상
n = 조절할 밝기 값
n이 양수이면 출력 영상 dst의 전체적인 밝기가 증가하고, n이 음수이면 밝기가 감소하여 어두워진다.
하지만 실제 영상의 밝기 조절을 구현할 때는 포화 연산을 함께 고려한 수식을 사용해야한다.
포화 연산을 고려한 수식: dst(x, y) = saturate(src(x, y) + n)
- lenna.bmp 영상의 밝기를 100만큼 증가시켜 화면에 출력하는 예제
void brightness1()
{
// lenna.bmp 레나 영상을 그레이스케일 형식으로 불러와 src에 저장
Mat src = imread("lenna.bmp", IMREAD_GRAYSCALE);
// lenna.bmp 파일 불러오기가 실패하면 에러 메시지를 출력하고 종료
if (src.empty()) {
cerr << "Image load failed!" << endl;
return;
}
//src 영상의 모든 픽셀 값을 100만큼 증가시킨 결과 영상으로 dst에 저장
Mat dst = src + 100;
// src와 dst 영상을 각각 새 창에 출력하고 키 입력이 있을 때까지 기다리기
imshow("src", src);
imshow("dst", dst);
waitKey();
// 영상 출력 창을 모두 닫습니다.
destroyAllWindows();
}
5.1.3 영상의 밝기 조절 직접 구현하기
- 픽셀 값을 직접 참조하여 레나 영상의 밝기를 100만큼 증가시키는 예제
void brightness2()
{
// lenna.bmp 레나 영상을 그레이스케일 형태로 불러와 src에 저장
Mat src = imread("lenna.bmp", IMREAD_GRAYSCALE);
if (src.empty()) {
cerr << "Image load failed!" << endl;
return;
}
// 입력 영상 src와 크기, 타입이 같은 결과 영상 dst를 생성
Mat dst(src.rows, src.cols, src.type());
// 영상 전체를 스캔하면서 입력 영상의 픽셀 값에 100을 더하여 결과 영상 픽셀 값으로 설정
for (int j = 0; j < src.rows; j++) {
for (int i = 0; i < src.cols; i++) {
dst.at<uchar>(j, i) = src.at<uchar>(j, i) + 100;
}
}
imshow("src", src);
imshow("dst", dst);
waitKey();
destroyAllWindows();
}
template<> inline
uchar saturate_cast<uchar>( int v );
- v : int 자료형이 표현할 수 있는 범위의 정수
- 반환값: 0 ~ 255 사이의 정수
- bringtness2() 함수에서 포화 연산이 추가된 코드
void brightness3()
{
Mat src = imread("lenna.bmp", IMREAD_GRAYSCALE);
if (src.empty()) {
cerr << "Image load failed!" << endl;
return;
}
Mat dst(src.rows, src.cols, src.type());
for (int j = 0; j < src.rows; j++) {
for (int i = 0; i < src.cols; i++) {
// 밝기 조절된 픽셀 값에 saturate_cast() 함수를 이용하여 포화 연산을 수행한 수 결과 영상 픽셀 값으로 설정
dst.at<uchar>(j, i) = saturate_cast<uchar>(src.at<uchar>(j, i) + 100);
}
}
imshow("src", src);
imshow("dst", dst);
waitKey();
destroyAllWindows();
}
5.1.4 트랙바를 이용한 영상의 밝기 조절
- 결과 영상 출력창에 트랙바를 부착하고, 트랙바가 움직일 때 트랙바 위치만큼의 밝기를 조절하는 코드
void brightness4()
{
Mat src = imread("lenna.bmp", IMREAD_GRAYSCALE);
if (src.empty()) {
cerr << "Image load failed!" << endl;
return;
}
// 결과 영상을 출력하고 트랙바를 부착할 dst 창을 미리 생성
namedWindow("dst");
// dst 창에 트랙바를 부착하고 콜백 함수 on_brightness를 등록
// 입력 영상 src의 주소를 트랙바 콜백 함수의 사용자 데이터로 설정
createTrackbar("Brightness", "dst", 0, 100, on_brightness, (void*)&src);
// 프로그램 실행 시 dst 창에 레나 영상이 정상적으로 표시되도록 강제로 on_brigthness() 함수를 호출
on_brightness(0, (void*)&src);
waitKey();
destroyAllWindows();
}
// 트랙바 콜백 함수에서 밝기 조절된 결과 영상 dst를 화면에 출력
void on_brightness(int pos, void* userdata)
{
Mat src = *(Mat*)userdata;
Mat dst = src + pos;
imshow("dst", dst);
}
5.2 영상의 명암비 조절
5.2.1 기본적인 명암비 조절 방법
- 명암비가 낮은 영상: 사물 간의 밝기 차이가 크지 않아 흐릿하고 탁한 느낌
- 명암비가 높은 영상: 밝은 것은 밝게, 어두운 것은 충분히 어둡게 표현되어서 사물의 구분이 뚜렷하고
눈으로 보기에 선명한 느낌을 받는다. - 기본적인 명암비 조절 수식: dst(x, y) = saturate(s*src(x, y))
- src: 입력 영상
- dst: 출력 영상
- 상수 s: 0보다 큰 양의 실수
- 입력 영상 픽셀 값에 상수 s를 곱한 결과가 255보다 커지는 경우가 발생할 수 있으므로
포화 연산도 함께 사용해야함
- 입력 영상의 모든 픽셀 값에 2를 곱하여 결과 영상을 생성하고 화면에 출력하는 예제 코드
void contrast1()
{
Mat src = imread("lenna.bmp", IMREAD_GRAYSCALE);
if (src.empty()) {
cerr << "Image load failed!" << endl;
return;
}
// 입력 영상 src의 모든 픽셀 값에 2.0을 곱하여 결과 영상 dst를 생성
float s = 2.f;
Mat dst = s * src;
imshow("src", src);
imshow("dst", dst);
waitKey();
destroyAllWindows();
}
5.2.2 효과적인 명암비 조절 방법
- 명암비를 효과적으로 높이기 위해서는 밝은 픽셀은 더 밝게, 어두운 픽셀은 더 어두워지게 변경해야 함
- 이때 밝고 어둡다는 기준을 어떻게 설정할 것인지에 따라 명암비 조절 결과 영상의 품질 차이를 가져올 수 있음
- 그레이스케일의 중간값인 128을 기준으로 명암비를 조절하는 픽셀 값 변경 수식
: dst(x, y) = src(x, y) + (src(x, y) - 128) - a (알파)- 알파: -1보다 같거나 큰 실수
- 항상 (128, 128) 좌표를 지나가는 직선의 방정식
- 포화 연산을 포함한 명암비 조절 수식: dst(x, y) = saturate(src(x, y) + (src(x, y) - 128) - a)
- 대부분의 영상에서 픽셀값이 0 또는 255에 가까운 픽셀보다 중간 밝기의 픽셀이 많기 때문에
포화 연산의 영향을 받는 픽셀은 많지 않다. - a 값 1.0을 사용하여 레나 영상의 명암비를 증가시키는 소스 코드
void contrast2()
{
Mat src = imread("lenna.bmp", IMREAD_GRAYSCALE);
if (src.empty()) {
cerr << "Image load failed!" << endl;
return;
}
// 효과적인 영상의 명암비 조절 수식을 그대로 cpp 소스코드로 형태로 변환한 코드이며
// 입력 영상 src로부터 명암비가 증가된 결과 영상 dst를 생성
float alpha = 1.f;
Mat dst = src + (src - 128) * alpha;
imshow("src", src);
imshow("dst", dst);
waitKey();
destroyAllWindows();
}
5.3 히스토그램 분석
5.3.1 히스토그램 구하기
- 영상의 히스토그램(histogram): 영상의 픽셀 값 분포를 그래프 형태로 나타낸 것
- 빈(bin): 히스토그램 그래프에서의 가로축
- 그레이스케일 영상의 경우에는 256개의 빈을 갖는 히스토그램을 구하는 것이 일반적
- 빈 개수가 적으면 히스토그램이 표현하는 영상의 픽셀 값 분포 모양이 좀 더 대략적인 형태로
- 빈 개수가 많으면 세밀한 픽셀 값 분포 표현이 가능하다
void calcHist(const Mat* images, int nimages, const int* channels, InputArray mask, OutputArray his, int dims, cons t int* hisSize, const float** ranges, bool uniform = true, bool accumulate = false);
- images: 입력 영상의 배열 또는 입력 영상의 주소, 영상의 배열인 경우, 모든 영상의 크기와 깊이는 같아야 함
- nimages: 입력 영상 개수
- channels: 히스토그램을 구할 채널을 나타내는 정수형 배열
- mask: 마스크 영상. 입력 영상과 크기가 같은 8비트 배열이어야 함. 마스크 행렬의 원소 값이 0이 아닌 좌표의 픽셀만 히스토그램 계산에 사용됨. mask 인자에 Mat() 또는 noArray()를 지정하면 입력 영상 전체에 대해 히스토그램을 구함
- hist: 출력 히스토그램 CV_32F 깊이를 사용하는 dims-차원의 행렬
- dims: 출력 히스토그램의 차원 수
- histSize: 각 차원의 히스토그램 배열 크기를 나타내는 배열 (즉, 각 차원의 히스토그램 빈 개수를 나타내는 배열)
- ranges: 각 차원의 히스토그램 범위, 등간격 히스토그램이면 (uniform = true) . range[i]는 각 차원의 최솟값과 최댓값으로 구성된 배열이고 [최솟값, 최댓값) 범위를 나타낸다. 비등간격 히스토그램이면 (uniform = false).range[i]는 각각의 구역을 나타내는 histSize[i] + 1개의 원소로 구성된 배열
- uniform: 히스토그램 빈의 간격이 균등한지를 나타내는 플래그
- accumulate: 누적 플래그. 이 값이 true이면 hist 배열을 초기화하지 않고 누적하여 히스토그램을 계산한다
- calcHist() 함수를 사용하는 예제 코드
Mat calcGrayHist(const Mat& img)
{
// CV_Assert() 매크로 함수를 이용하여 calcGrayHist() 함수로 전달된 img 영상이 그레이스케일 영상인지 검사
// 만약 img 영상이 그레이스케일 영상이 아니면 에러가 발생하며 프로그램이 종료됨
CV_Assert(img.type() == CV_8UC1);
// 12행에서 호출하는 calcHist() 함수에 전달할 인자를 생성하는 구문
Mat hist; // 히스토그램 정보를 저장할 Mat 타입의 변수 hist 를 선언
int channels[] = { 0 }; // 히스토그램을 구할 채널 번호를 담은 channels 배열 생성.
// 그레이스케일 영상은 한개의 채널을 가지고 있기 때문에 배열은 원소 0 하나만 가진다.
int dims = 1; // dims 변수에 1을 대입한 것은 하나의 채널에 대해서만 히스토그램을 구하기 때문에,
// 결과로 구해지는 hist 행렬이 1차원 행렬임을 나타냄
const int histSize[] = { 256 }; // histSize 배열 원소에 256을 하나 지정한다는 것은
// 입력 영상의 첫번째 채널 값의 범위를 256개 빈으로 나누어 히스토그램을 구하겠다는 의미
// graylevel 배열의 원소에는 그레이스케일 값의 최솟값과 최댓값인 0과 256을 차례대로 지정.
// ranges 배열은 graylevel 배열 이름을 원소로 갖는 배열
float graylevel[] = { 0, 256 };
const float* ranges[] = { graylevel };
// calcHist() 함수를 이용하여 img 영상의 히스토그램을 구하고, 그 결과를 hist 변수에 저장
calcHist(&img, 1, channels, noArray(), hist, dims, histSize, ranges);
// 구해진 히스토그램 hist를 반환
return hist;
}
- calcHist() 함수로 구한 히스토그램 행렬을 막대그래프 형태로 나타내려면 직접 hist 행렬을 참조하여
막대그래프 영상을 생성해야한다. - 히스토그램 그래프에서 최대 빈도수를 표현하는 막대그래프 길이가 100픽셀이 되도록 그래프를 그리는 예제
Mat getGrayHistImage(const Mat& hist)
{
// getGrayImage() 함수의 인자로 전달된 hist 행렬이 256개의 빈으로 구성된 히스토그램 행렬인지 검사
CV_Assert(hist.type() == CV_32FC1);
CV_Assert(hist.size() == Size(1, 256));
// hist 행렬 원소의 최댓값을 histMax 변수에 저장
double histMax;
minMaxLoc(hist, 0, &histMax);
// 흰색으로 초기화된 256x100 크기의 새 영상 imgHist를 생성
Mat imgHist(100, 256, CV_8UC1, Scalar(255));
// for 반복문과 line() 함수를 이용하여 각각의 빈에 대한 히스토그램 그래프를 그림
for (int i = 0; i < 256; i++) {
line(imgHist, Point(i, 100),
Point(i, 100 - cvRound(hist.at<float>(i, 0) * 100 / histMax)), Scalar(0));
}
// hist 행렬로부터 구한 256x100 크기의 히스토그램 영상 imgHist를 반환
return imgHist;
}
5.3.2 히스토그램 스트레칭
- 히스토그램 스트레칭(histogram stretching): 영상의 히스토그램이 그레이스케일 전 구간에 걸쳐서
나타나도록 변경하는 선형 변환 기법 - 보통 명암비가 낮은 영상은 히스토그램이 특정 구간에 집중되어 나타나게 되는데, 이러한 히스토그램을
마치 고무줄을 잡아 늘이듯이 펼쳐서 히스토그램 그래프가 그레이스케일 전 구간에 나타나도록 변환하는 기법 - 히스토그램 스트레칭 수식:
- src: 입력 영상
- dst: 출력 영상
- Gmin: 입력 영상의 픽셀값 중에서 가장 작은 그레이스케일 값
- Gmax: 입력 영상의 픽셀값 중에서 가장 큰 그레이스케일 값
- 이 그레이스케일을 양방향으로 늘려서 Gmin은 0이 되게 하고 Gmax는 255가 되도록 함
- hawkes.bmp 입력 영상에 대해 히스토그램 스트레칭을 수행하고, 해당 히스토그램 그래프를 화면에 출력하는 예제
void histgoram_stretching()
{
// hawkes.bmp 파일을 그레이스케일 형식으로 불러와서 src에 저장
Mat src = imread("hawkes.bmp", IMREAD_GRAYSCALE);
if (src.empty()) {
cerr << "Image load failed!" << endl;
return;
}
// 입력 영상 src에서 그레이스케일 최솟값과 최댓값을 구하여 gmin과 gmax에 저장
double gmin, gmax;
minMaxLoc(src, &gmin, &gmax);
// 히스토그램 스트레칭 수식을 그대로 적용하여 결과 영상 dst를 생성
Mat dst = (src - gmin) * 255 / (gmax - gmin);
// 입력 영상과 히스토그램 스트레칭 결과 영상, 그리고 각각의 히스토그램을 화면에 출력
imshow("src", src);
imshow("srcHist", getGrayHistImage(calcGrayHist(src)));
imshow("dst", dst);
imshow("dstHist", getGrayHistImage(calcGrayHist(dst)));
waitKey();
destroyAllWindows();
}
5.3.3 히스토그램 평활화
- 히스토그램 평활화(histogram equlization): 영상의 픽셀 값 분포가 그레이스케일 전체 영역에서 골고루 나타나도록
변경하는 알고리즘의 하나 - 히스토그램 그래프에서 특정 그레이스케일 값 근방에서 픽셀 분포가 너무 많이 뭉쳐 있는 경우 이를 넓게 펼쳐 주는 방식으로 픽셀 값 분포를 조절
- OpenCV는 그레이스케일 영상의 히스토그램 평활화를 수행하는 equalizeHist() 함수를 제공
void equalizeHist( InputArray src, OutputArray dst );
- src: 입력 영상. 8비트 1채널
- dst: 출력 영상. src와 크기와 타입이 같다.
- 평활화 전후의 영상과 히스토그램 그래프를 화면에 출력하는 예제 코드
void histgoram_equalization()
{
// hawkes.bmp 파일을 그레이스케일 형태로 불러와서 src에 저장
Mat src = imread("hawkes.bmp", IMREAD_GRAYSCALE);
if (src.empty()) {
cerr << "Image load failed!" << endl;
return;
}
// 히스토그램 평활화를 수행한 결과를 dst에 저장
Mat dst;
equalizeHist(src, dst);
// 입력 영상과 히스토그램 평활화 결과 영상, 그리고 각각의 히스토그램을 화면에 출력
imshow("src", src);
imshow("srcHist", getGrayHistImage(calcGrayHist(src)));
imshow("dst", dst);
imshow("dstHist", getGrayHistImage(calcGrayHist(dst)));
waitKey();
destroyAllWindows();
}
'3학년 > OpenCV' 카테고리의 다른 글
[OpenCV] 8장 영상의 기하학적 변환 (0) | 2023.07.01 |
---|---|
[OpenCV] 7장 필터 (0) | 2023.06.30 |
[OpenCV] 6장 영상의 산술 및 논리 연산 (0) | 2023.05.28 |
[OpenCV] 4장 OpenCV 주요 기능 (0) | 2023.05.22 |
[OpenCV] 3장 OpenCV 주요 클래스 (0) | 2023.05.22 |