1 圖像
圖像是人類視覺的基礎,是物體反射或透射光的分佈,我們認爲圖像就是光的矩陣,矩陣上點的值代表光的強度,這些點被稱爲像素點(pixel)。
彩色圖像可看做是色彩空間中的三維張量,比如RGB色彩空間,RGB信息分別存放在3個獨立的“通道(channel)”上。
typedef struct {
int w, h, c;
float *data;
} image;
2 圖像縮放和插值
圖像縮放是指對圖像大小進行調整。
比如 圖像放大:
圖像縮放的任務就是確定每一個新的像素點的值。方法是:
- 匹配像素點座標。
- 用某種算法確定新的像素點的值。
2.1 匹配像素點座標
在最鄰近算法(nearest neighbor)和雙線性插值算法(bilinear interpolation)中,要選取變換座標後像素點鄰近4個像素點進行新像素點值的計算,像素點的匹配:
在雙三次插值算法中(bicubric interpolation),要選取變換座標後鄰近的16個像素點進行線性插值,像素點的匹配:
雙三次插值算法比較複雜,這裏介紹最鄰近算法和雙線性插值算法的像素點匹配:
對於新圖像上的某個像素點,可以匹配到原圖像中的座標位置:
2.2 最鄰近算法
最鄰近算法就是匹配到原來的座標後,選擇距離該座標最近的像素點的值代表該新的像素點的值。
C語言實例代碼:
float nn_interpolate(image im, float x, float y, int c)
{
// get nearest neighbor
int i = round(x);
int j = round(y);
return get_pixel(im, i, j, c);
}
image nn_resize(image im, int w, int h)
{
if (!w || !h)
return make_image(1,1,1);
// (w, h) -> (im.w, im.h)
// a * X + b -> Y
float w_a, w_b, h_a, h_b;
w_a = (float)im.w / w;
w_b = 0.5 * (w_a - 1);
h_a = (float)im.h / h;
h_b = 0.5 * (h_a - 1);
image ret = make_image(w, h, im.c);
for (int k = 0; k < im.c; k++) {
for (int j = 0; j < h; j++) {
for (int i = 0; i < w; i++) {
// map coordinates to orignal
float x = w_a * i + w_b;
float y = h_a * j + h_b;
float val = nn_interpolate(im, x, y, k);
set_pixel(ret, i, j, k, val);
}
}
}
return ret;
}
2.3 雙線性插值算法
雙線性插值算法是在匹配的像素點上通過鄰近的4個像素點進行線性插值。
C語言示例代碼:
float bilinear_interpolate(image im, float x, float y, int c)
{
// the 4 point covering the pixel
int w_floor = floor(x);
int w_ceil = ceil(x);
int h_floor = floor(y);
int h_ceil = ceil(y);
float val_left_up = get_pixel(im, w_floor, h_floor, c);
float val_right_up = get_pixel(im, w_ceil, h_floor, c);
float val_left_down = get_pixel(im, w_floor, h_ceil, c);
float val_right_down = get_pixel(im, w_ceil, h_ceil, c);
float h1 = y - h_floor;
float h2 = 1 - h1;
float q1 = val_left_up * h2 + val_left_down * h1;
float q2 = val_right_up * h2 + val_right_down * h1;
float w1 = x - w_floor;
float w2 = 1 - w1;
float val = q1 * w2 + q2 * w1;
return val;
}
image bilinear_resize(image im, int w, int h) {
if (!w || !h)
return make_image(1,1,1);
// (w, h) -> (im.w, im.h)
// a * X + b -> Y
float w_a, w_b, h_a, h_b;
w_a = (float)im.w / w;
w_b = 0.5 * (w_a - 1);
h_a = (float)im.h / h;
h_b = 0.5 * (h_a - 1);
image ret = make_image(w, h, im.c);
for (int k = 0; k < im.c; k++) {
for (int j = 0; j < h; j++) {
for (int i = 0; i < w; i++) {
// map coordinates to orignal
float x = w_a * i + w_b;
float y = h_a * j + h_b;
float val = bilinear_interpolate(im, x, y, k);
set_pixel(ret, i, j, k, val);
}
}
}
return ret;
}
3 卷積
卷積是圖像處理中強大且廣泛使用的技術。我們將兩個函數 和 的卷積定義爲積分:
該積分可以描述爲:點擴散函數(PSF) 對函數 的每個點的作用的累積。
把卷積用於數字圖像中時,上面的公式要有兩個變化:
- 要使用雙積分,因爲數字圖像每個通道有2維度;
- 積分必須改成離散求和。
數字圖像上的卷積公式爲:
其中 被稱爲卷積模板(function),也稱卷積核(kernel)、過濾器(filter)。在使用卷積模板前必須將其翻轉(這個是由卷積的定義而來),所以我們可以預先翻轉(以後代碼或公式中的卷積模板都是已經預先翻轉過的):
再得到卷積公式:
即將修改後的卷積模板和對應的鄰近值相乘求和。在 的鄰域中卷積模板可以表達爲:
卷積算法爲:
for all pixels in image do {
Q0 = P0*h0 + P1*h1 + P2*h2 + P3*h3 + P4*h4
+ P5*h5 + P6*h6 + P7*h7 + P8*h8;
}
C語言示例代碼:
/*
* This convolution basic use same width and height as orignal image, stride is 1.
* "preserve = 1" represents im keeps its channel, or it change to 1 channel
*/
image convolve_image(image im, image filter, int preserve)
{
assert(filter.c == 1 || im.c == filter.c);
// get the result image channel after convolution
int channel;
if(preserve == 1) {
channel = im.c;
} else {
channel = 1;
}
// result image
image convoluted_image = make_image(im.w, im.h, channel);
float sum = 0;
for (int j = 0; j < im.h; j ++) {
for (int i = 0; i < im.w; i++) {
// find the match neibor pixels idx
int min_x = i - filter.w / 2;
int min_y = j - filter.h / 2;
// sum the kernel
sum = 0;
for (int k = 0; k < im.c; k++) {
float filter_pix, im_pix;
for (int jj = 0; jj < filter.h; jj++) {
for (int ii = 0; ii < filter.w; ii++) {
int im_x = min_x + ii;
int im_y = min_y + jj;
im_pix = get_pixel(im, im_x, im_y, k);
if (filter.c >1) {
filter_pix = get_pixel(filter, ii, jj, k);
} else {
filter_pix = get_pixel(filter, ii, jj, 0);
}
sum += filter_pix * im_pix;
}
}
// if convoluted_image's channel > 1, the kernel sum should be seperate in each channel
if (channel > 1) {
set_pixel(convoluted_image, i, j, k, sum);
sum = 0;
}
}
// if convoluted_image's channel == 1, the kernel sum should contain all the channels
if (channel == 1) {
set_pixel(convoluted_image, i, j, 0, sum);
}
}
}
// clamp image
// clamp_image(convoluted_image);
return convoluted_image;
}
常見簡單卷積核有:
卷積核 | 名稱 | 作用 |
Highpass Kernel | 邊緣檢測 | |
Sharpen Kernel | 銳化圖片 | |
Emboss Kernel | stylin' |