CV(2): 圖像、縮放和卷積

1 圖像

  圖像是人類視覺的基礎,是物體反射或透射光的分佈,我們認爲圖像就是光的矩陣,矩陣上點的值代表光的強度,這些點被稱爲像素點(pixel)
  彩色圖像可看做是色彩空間中的三維張量,比如RGB色彩空間,RGB信息分別存放在3個獨立的“通道(channel)”上。

  我們可以定義簡單數據結構存儲圖像數據:
typedef struct {
	int w, h, c;
	float *data;
} image;

2 圖像縮放和插值

  圖像縮放是指對圖像大小進行調整。
比如 4×47×74\times4\rightarrow7\times7圖像放大:

圖像縮放的任務就是確定每一個新的像素點的值。方法是:

  1. 匹配像素點座標。
  2. 用某種算法確定新的像素點的值。

2.1 匹配像素點座標

最鄰近算法(nearest neighbor)和雙線性插值算法(bilinear interpolation)中,要選取變換座標後像素點鄰近4個像素點進行新像素點值的計算,像素點的匹配:

aX+b=YaX + b = Y

雙三次插值算法中(bicubric interpolation),要選取變換座標後鄰近的16個像素點進行線性插值,像素點的匹配:

f(x)=a+bx+cx2+dx3f(x) = a + bx+cx^2+dx^3

  雙三次插值算法比較複雜,這裏介紹最鄰近算法和雙線性插值算法的像素點匹配:

(0.5,0.5)(0.5,0.5)(6.5,6.5)(3.5,3.5)0.5×a+b=0.56.5×a+b=3.5a=47,b=314 (-0.5, -0.5) \rightarrow(-0.5,-0.5) \\ (6.5, 6.5) \rightarrow(3.5,3.5) \\ -0.5\times a + b = -0.5 \\ 6.5\times a + b = 3.5 \\ \Rightarrow a = \frac{4}{7}, b = -\frac{3}{14}
對於新圖像上的某個像素點(1,3)(1, 3),可以匹配到原圖像中的座標位置:
i=47×1314=514j=47×3314=2114 i = \frac{4}{7}\times1 - \frac{3}{14} = \frac{5}{14} \\ j = \frac{4}{7}\times3 - \frac{3}{14} = \frac{21}{14}

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個像素點進行線性插值。

q1=V1×d2+V2×d1\quad\quad q1 = V1\times d2 + V2\times d1
q2=V3×d2+V4×d1\qquad q2 = V3\times d2 + V4\times d1
q=q1×d4+q2×d3=V1×A1+V2×A2+V3×A3+V4×A4 \begin{aligned} \quad q & = q1\times d4 + q2\times d3 \\ & = V1\times A1 + V2\times A2 + V3\times A3 + V4\times A4 \end{aligned}

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 卷積

  卷積是圖像處理中強大且廣泛使用的技術。我們將兩個函數 f(x)f(x)g(x)g(x) 的卷積定義爲積分:
f(x)g(x)=f(u)g(xu)du f(x)*g(x)=\int_{-\infty}^{\infty}f(u)g(x-u)\,du

  該積分可以描述爲:點擴散函數(PSF) g(x)g(x) 對函數 f(x)f(x) 的每個點的作用的累積。
  把卷積用於數字圖像中時,上面的公式要有兩個變化:

  1. 要使用雙積分,因爲數字圖像每個通道有2維度;
  2. 積分必須改成離散求和。

  數字圖像上的卷積公式爲:
F(x,y)=f(x,y)g(x,y)=ijf(i,j)g(xi,yj) F(x,y)=f(x,y)*g(x,y)=\sum_i\sum_jf(i,j)g(x-i,y-j)

其中 gg 被稱爲卷積模板(function),也稱卷積核(kernel)過濾器(filter)。在使用卷積模板前必須將其翻轉180o180^o(這個是由卷積的定義而來),所以我們可以預先翻轉(以後代碼或公式中的卷積模板都是已經預先翻轉過的):
h(x,y)=g(x,y) h(x,y)=g(-x,-y)

再得到卷積公式:
F(x,y)=ijf(x+i,y+j)h(i,j) F(x,y)=\sum_i\sum_jf(x+i,y+j)h(i,j)

即將修改後的卷積模板和對應的鄰近值相乘求和。在 3×33\times 3 的鄰域中卷積模板可以表達爲:
[h4h3h2h5h0h1h6h7h8] \begin{bmatrix} h4 & h3 & h2 \\ h5 & h0 & h1 \\ h6 & h7 & h8 \end{bmatrix}

卷積算法爲:

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'
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章