Intensity Transformation
개념
Definition
input 영상의 각 픽셀의 intensity 값을 수학적 표현을 통해 해당하는 intensity 값으로 매핑하는 과정

Example of intensity transformation

Image negatives
이미지의 intensity 값의 범위가 [0, L-1] 일 때, 이미지의 negative는
(s:output, r:input)이 된다.
어두운 배경에 포함된 흰색, 회색의 디테일을 강조하는 데 유용하다

Log transformation
(c:constant, r:input, s:output)
r이 0이 될 수 있지만 log 0은 허용되지 않으므로 1을 더하여 계산
좁은 범위의 intensity 값을 넓은 범위 값으로 매핑한다 → 어두운 영역의 미세한 디테일이 강조 (대비 향상)
어두운 영역의 대비를 향상시킨다

Power-Law (Gamma) Transformation
(c:constant, s:output, r:input)

gamma 값에 따라 이미지의 어두운 픽셀의 범위가 확장되거나 그 반대가 된다.

Gamma를 1보다 작은 값으로 설정하는 것이 input 이미지가 밝아지는 것이 아니라, 어두운 영역의 디테일을 증가시키는 것이다
Piecewise-linear transformation (구간별 선형 변환)
transformation 함수를 더 복잡하게 구성할 수 있다.

Thresholding (임계값)을 기준으로 두개의 선형 구간을 나누어 흑백으로만 구분할 수도 있다.

Example Results

소스 코드 예제
Pixel Access
operator
image.at<DATA_TYPE>(WANT_ROW, WANT_COL) 을 통해 원하는 픽셀에 접근할 수 있다.
void pixel_access_by_operator() {
Mat image, image_gray;
int value, value_B, value_G, value_R, channels;
image = imread("lena.png");
image_gray = imread("lena.png",0);
// at operator
value = image_gray.at<uchar>(50, 100);
cout << "value : " << value << endl;
value_B = image.at<Vec3b>(50, 100)[0];
value_G = image.at<Vec3b>(50, 100)[1];
value_R = image.at<Vec3b>(50, 100)[2];
cout << "value at (100,50): " << value_B << " " << value_G << " " << value_R
<< endl;
waitKey(0);
}pointer
pointer는 at 연산자를 사용하는 것보다 빠르다.
void pixel_access_by_pointer() {
Mat image = imread("lena.png");
int value, value_B, value_G, value_R, channels;
channels = image.channels();
// pointer
uchar* p;
p = image.ptr<uchar>(50);
value_B = p[100 * channels + 0];
value_G = p[100 * channels + 1];
value_R = p[100 * channels + 2];
cout << "value at (100,50): " << value_B << " " << value_G << " " << value_R
<< endl;
waitKey(0);
}data function
void pixel_access_by_data() {
Mat image = imread("lena.png");
int value, value_B, value_G, value_R, channels;
channels = image.channels();
uchar* data = (uchar*)image.data;
value_B = data[(50 * image.cols + 100) * channels + 0];
value_G = data[(50 * image.cols + 100) * channels + 1];
value_R = data[(50 * image.cols + 100) * channels + 2];
cout << "value at (100,50): " << value_B << " " << value_G << " " << value_R
<< endl;
waitKey(0);
}image.ptr<uchar>(50) 은 이미지의 50번째 행의 시작주소를 반환한다.
p가 50번째 행의 첫번째 픽셀 데이터를 가리키고 있는데,
image의 channel은 3이므로, 50번째 행에서 100번째 열을 가리키는 데이터는 p[100 * channel]이 될 것이고, 이 값에서 +0, 1, 2 한 것이 각각 B, G, R 값을 나타낸다.
data member function
DATA_TYPE* data = (DATA_TYPE*)image.data;
data[WANT_ROW * image.cols + WANT_COL]
위처럼 data를 원하는 타입으로 형변환 후 ROW와 COL 값으로 접근할 수 있다.
Intensity transformation
gray-scale 이미지로 negative, log, gamma transformation 예제 코드를 설명
Image negative
void image_negative() {
Mat image = imread("lena.png", 0);
Mat negative_img = image.clone();
for (int j = 0; j < image.rows; j++)
for (int i = 0; i < image.cols; i++)
negative_img.at<uchar>(j, i) = 255 - image.at<uchar>(j, i);
imshow("Input image", image);
imshow("Negative transformation", negative_img);
waitKey(0);
}반복문으로 각 row와 col을 순회하며 at 연산자를 실행하여 깊은 복사를 시행한 Mat 객체의 값을 바꿔준다.

Log transformation
void log_transformation() {
Mat image = imread("lena.png", 0);
Mat f_img, log_img;
double c = 1.5f; // scale constant
// log 계산을 위해 float 타입으로 변환
image.convertTo(f_img, CV_32F);
f_img = abs(f_img) + 1; // 0 이하 값이 없도록 절댓값 처리
log(f_img, f_img); // 각 픽셀에 로그 연산
// 로그 연산의 결과를 0~255 범위로 다시 맞춤
normalize(f_img, f_img, 0, 255, NORM_MINMAX);
convertScaleAbs(f_img, log_img,
c); // scaling by c, conversion to an unsigned 8-bit type
imshow("Input image", image);
imshow("Log transformation", log_img);
waitKey(0);
}
Gamme Correction
void gamma_correction() {
Mat image = imread("lena.png", 0);
Mat gamma_img;
MatIterator_<uchar> it, end;
float gamma = 0.5;
unsigned char pix[256];
for (int i = 0; i < 256; i++) {
pix[i] = saturate_cast<uchar>(pow((float)(i / 255.0), gamma) * 255.0f);
}
gamma_img = image.clone();
for (int j = 0; j < image.rows; j++)
for (int i = 0; i < image.cols; i++)
gamma_img.at<uchar>(j, i) = pix[gamma_img.at<uchar>(j, i)];
imshow("Input image", image);
imshow("Gamma transformation", gamma_img);
waitKey(0);
}위 예제에서는 pix[256] 배열을 사용해 불필요한 추가 연산없이 픽셀 값을 빠르게 변환하도록 한다.
이렇게 한 번 계산해둔 배열을 룩업 테이블 LUT 이라고 한다.
0~255 값인 i에 대해 i/255.0 연산을 통해 값의 범위를 0~1로 정규화한다.
정규화된 값에 대해 gamma 지수로 pow() 연산을 해도 여전히 값은 0~1 범위이다.
다시 *255.0으로 0~255 범위 값으로 되돌린다.
saturate_cast<uchar> 로 정수형으로 변환한다.

Spatial Filtering
이미지의 각 픽셀 값을 주변의 픽셀과 결합해서 새 값으로 매핑
이때 사용되는 필터를 spaital mask, kernel, template, window 등으로 부른다


example

빨간색, 파란색, 초록색 픽셀에 대해 우측 행렬을 통해 spatial filtering을 적용했을 때 값은 아래와 같다.
Average filter
작은 filter mask 행렬을 각 픽셀마다 순회하며 마스크 영역 내의 모든 픽셀 강도 값의 평균을 계산하여 중심 픽셀 값을 교체한다.
**low pass filter (저역 통과 필터)**라고도 부른다.
random noise를 감소시키지만 이미지를 흐릿하게 만든다 (blur)

Gaussian filter
Gaussian function을 사용해 영상을 부드럽게 만드는 필터

Mask Size
spatial filtering을 적용할 때 mask 의 사이즈는 중요하다.
작은 물체를 블러처리 할 때는 작은 사이즈의 mask를 사용해야 한다.
mask 사이즈가 커질수록 연산 비용이 증가한다.

Sharpening
sharpening은 영상의 intensity 변화를 강조한다.
**Spatial differentiation (공간 미분)**을 통해 수행한다.

Sharpening은 영상의 intensity 변화를 측정한다고 했다.

**second derivative (2차 미분)**을 이용해 강도가 급격하게 변하는 edge, line 부분을 찾아낸다.
Sharpening 의 과정은 다음과 같다
- Input 영상의 second derivative 값을 계산
- second derivative 값을 input 이미지에 더하기

sharpening using unsharp masking

Median filter
Median value
3X3 에서 중앙값은 5번째로 큰 값이고, 5X5 에서 중앙은 13번째로 큰 값이다.
마스크 영역 내의 모든 픽셀 값을 크기 순으로 정렬하고 중앙값으로 중심 픽셀의 값을 대체한다.
