Image Kernel
Image Processing 관련한 문서를 검색하면 일단 용어 자체가 그리 익숙하지 않은 상황에서 kernel, convolution matrix, mask 등의 용어를
접하기도 합니다.
사실 아주 오래전 Windows Application 을 구성하면서 데이터에 필터 ( 행렬 값 - kernel )를 적용하며, 시각화 하여 보여주는 프로그램을 개발한 적이 있었습니다.
Application 에서만, 그것도 범용적이지 않은 프로그램에서 사용하던 기능들이었는데, 이제는 Web 에서 이런 기능이 가능한 것을 보면서, 개발환경의 변화를 새삼스레 느끼게 됩니다.
Copy & Paste 의 대명사 였던 javascript 가 이제는 어떤면에서는 가장 첨단을 달리는 언어로 탈바꿈한것을 보면, 어쩌면 당연한 일인지도 모르겠습니다.
새롭게 나오는 기능을 찾아서 공부하지 않으면, 시대에 뒤 떨어질 정도로 질과 양적인 면에서 모두 달라지고 있는 느낌입니다. ^^
kernel 이란?
주변 픽셀 정보를 같이 계산하여 이미지를 변형 시키기 위한 행렬(Matrix) 입니다.
예를 들어 Box blur 라는 kernel 은 아래와 같이 모두 1로 구성된 행렬 입니다.
[
1, 1, 1,
1, 1, 1,
1, 1, 1
]
그림에서 가로 위치가 100번째 세로 위치가 100번째의 (x = 100, y = 100) 의 pixel 의 원색상이 하얀색 ( 255, 255, 255, 255 ) 의 불투명이라면 이 주변의 색상을 더하여 전체값인 9로 나누어
색상을 결정하게 됩니다. x = 99, y = 99 ~ x = 101, y = 101 범위의 box 영역을 대상으로 합니다. 이렇게 되면 색상이 주변색과 혼합되어 부드러워 지는 효과를 내게 된다는 것입니다.
이런 식으로 전체 색상을 재구성하면 kernel 의 구성에 따라 이미지가 다른 특성을 보이게 됩니다.
kernel 관련 소스
예시된 Kernel 은 위키의 내용을 기준으로 구성되었습니다.
Sobel Kernel , Canny Edge 등 제시된 내용 보다 심도 깊고, 다양한 방법이 있습니다.
사실 잘 아는 분야가 아니라서 위키에 제시된 Kernel 로 구성한 소스만 간단히 기재해 보도록 하겠습니다.
https://en.wikipedia.org/wiki/Kernel_(image_processing)
/**
*
* @param {*} typeNum => 1 : ridge, 2 : edge detection, 3 : sharpen, 4 : box blur
* , 5 : gaussian blur(3x3) , 6 : gaussian blur(5x5), 7 : unsharp masking(5x5 )
*
* @returns
*/
export const getImageKernelByType = ( typeNum ) => {
// Ridge
let result = undefined;
switch( typeNum ) {
case 1 : // ridge
result = new Float32Array( [
0, -1, 0,
-1, 4, -1,
0, -1, 0
]);
result.rows = 3;
result.cols = 3;
result.xPos = 1;
result.yPos = 1;
break;
case 2 :
result = new Float32Array( [
-1, -1, -1,
-1, 8, -1,
-1, -1, -1
]);
result.rows = 3;
result.cols = 3;
result.xPos = 1;
result.yPos = 1;
break;
case 3 :
result = new Float32Array( [
0, -1, 0,
-1, 5, -1,
0, -1, 0
]);
result.rows = 3;
result.cols = 3;
result.xPos = 1;
result.yPos = 1;
break;
case 4 :
result = new Float32Array( [
1/9, 1/9, 1/9,
1/9, 1/9, 1/9,
1/9, 1/9, 1/9
]);
result.rows = 3;
result.cols = 3;
result.xPos = 1;
result.yPos = 1;
break;
case 5 :
result = new Float32Array( [
1/16, 2/16, 1/16,
2/16, 4/16, 2/16,
1/16, 2/16, 1/16
]);
result.rows = 3;
result.cols = 3;
result.xPos = 1;
result.yPos = 1;
break;
case 6 :
result = new Float32Array( [
1/256, 4/256, 6/256, 4/256, 1/256,
4/256, 16/256, 24/256, 16/256, 4/256,
6/256, 24/256, 36/256, 24/256, 6/256,
4/256, 16/256, 24/256, 16/256, 4/256,
1/256, 4/256, 6/256, 4/256, 1/256,
]);
result.rows = 5;
result.cols = 5;
result.xPos = 2;
result.yPos = 2;
break;
case 7 :
result = new Float32Array( [
-1/256, -4/256, -6/256, -4/256, -1/256,
-4/256, -16/256, -24/256, -16/256, -4/256,
-6/256, -24/256, 476/256, -24/256, -6/256,
-4/256, -16/256, -24/256, -16/256, -4/256,
-1/256, -4/256, -6/256, -4/256, -1/256,
]);
result.rows = 5;
result.cols = 5;
result.xPos = 2;
result.yPos = 2;
break;
default :
result = new Float32Array( [
0, 0, 0,
0, 1, 0,
0, 0, 0
]);
result.rows = 3;
result.cols = 3;
result.xPos = 1;
result.yPos = 1;
break;
}
return result;
};
Convolution 진행 관련 소스
앞서 구성했던 Pixel 을 가져오는 부분과, 위 진행한 내용을 기준으로 데이터 Pixel 을 구성하는 소스 입니다.
export const executeConvolution = (typeNum, originalData, transData, paddingType) => {
if ( !originalData || !transData ) {
alert( "Data 확인이 필요합니다. ");
return transData;
}
const len = originalData.data.length;
if ( len != transData.data.length ) {
alert ( "Data 길이가 일치하지 않습니다. ");
return transData;
}
const kernel = getImageKernelByType(typeNum);
const kRows = kernel.rows;
const kCols = kernel.cols;
const xPos = kernel.xPos;
const yPos = kernel.yPos;
const width = originalData.width;
const height = originalData.height;
const calcPixels = new Float32Array(4);
for ( let r = 0; r < height; r++ ) {
for ( let c = 0; c < width; c++ ) {
for ( let t = 0; t < 4; t++ ) {
calcPixels[t] = 0;
}
for ( let kr = 0; kr < kRows; kr++ ) {
for ( let kc = 0; kc < kCols; kc++ ) {
let x = c+kc-xPos;
let y = r+kr-yPos;
let pixels = getPixels(x,y,paddingType,width,height,originalData);
let tdx = kr*kCols+kc;
for ( let t = 0; t < 3; t++ ) {
calcPixels[t] += (pixels[t]*kernel[tdx]);
}
}
}
const idx = r*width*4+c*4;
for ( let t = 0; t < 3; t++ ) {
let v = Math.round(calcPixels[t]);
v = (v > 255 ? 255 : (v < 0 ? 0 : v));
transData.data[idx+t] = v;
}
transData.data[idx+3] = originalData.data[idx+3];
}
}
return transData;
};