第10章 形を変える

大きさを変える

元画像
上の画像に対して、誤った方法、最近傍法、線形補完法による拡大縮小例を示す。

拡大・縮小(誤った方法)

縮小例
1/2縮小

拡大例
2倍拡大

縮小は問題ないのだが、拡大の場合、単純に各画素の座標を拡大した位置に移動させただけでは、 画素と画素の間がマッピングされないため、とびとびの画像になっているのが 拡大した画像をみるとよくわかる。

最近傍法による拡大・縮小

最近傍法による縮小
1/2縮小

最近傍法による拡大
2倍拡大

拡大時に画素がとびとびになる問題点が改善された。
拡大画像をみるとモザイク状になってしまっている のがわかる。これは、最近傍画素を拡大後の画素とするために、同じ明るさの画素が 連続するためである。

線形補間法による拡大・縮小

線形補完法による縮小
1/2縮小

線形補完法による拡大
2倍拡大

拡大するとモザイク状になってしまう問題が改善された。
拡大画像を見ると、画素が滑らかになっているのがわかる。 拡大後の画素は(10.3)式によって計算される。

位置を変える

位置を変える
(10.4)、(10.5)式に基づいて、画像をx方向に50,y方向に25移動させた。

回転させる

30°回転
(10.6)、(10.7)式に基づいて、反時計回りに30°回転させた。

複雑な変形をする

アフィン変換

アフィン変換
アフィン変換を用いることにより、一回の画像の読み込みで、 拡大・縮小、移動、回転を行った。
今回は、x方向に50、y方向に25、30°回転、1/2倍に縮小した。

透視変換

透視変換
透視変換を用いて変形を行った。
上の例は、図10.17のパラメータと同じく、 x方向に1.5倍、y方向に3.0倍、移動量はx,y,zいずれも0、 z方向に10°回転、x方向に-75°回転、y方向に10°回転させ、 視点を10、スクリーン座標を5に設定した。

プログラミング例

拡大・縮小(誤ったやり方)

#include        "Params.h"

/*--- scale_ng --- 拡大縮小(このやり方は,間違っている)----------------------
    image_in:    入力画像配列
    image_out:    出力画像配列
    zx:            拡大率(横)
    zy:            拡大率(縦)
-----------------------------------------------------------------------------*/
void scale_ng(unsigned char image_in[Y_SIZE][X_SIZE], 
    unsigned char image_out[Y_SIZE][X_SIZE], float zx, float zy)
{
    int    i, j, m, n;
    int    xs = X_SIZE/2;
    int    ys = Y_SIZE/2;

    for (i = -ys; i < ys; i++) {
        for (j = -xs; j < xs; j++) {
            m = (int)(zy * i);
            n = (int)(zx * j);
            if ( (m >= -ys) && (m < ys) && (n >= -xs) && (n < xs) )
                image_out[m+ys][n+xs] = image_in[i+ys][j+xs];
        }
    }
}

拡大・縮小(最近傍法)

#include    "Params.h"

/*--- scale_near --- 拡大縮小(最近傍法)--------------------------------------
    image_in:    入力画像配列
    image_out:    出力画像配列
    zx:            拡大率(横)
    zy:            拡大率(縦)
-----------------------------------------------------------------------------*/
void scale_near(unsigned char image_in[Y_SIZE][X_SIZE], 
    unsigned char image_out[Y_SIZE][X_SIZE], float zx, float zy)
{
    int    i, j, m, n;
    int    xs = X_SIZE/2;
    int    ys = Y_SIZE/2;

    for (i = -ys; i < ys; i++) {
        for (j = -xs;j < xs; j++) {
            if (i > 0) m = (int)(i/zy + 0.5);
            else m = (int)(i/zy - 0.5);
            if (j > 0) n = (int)(j/zx + 0.5);
            else n = (int)(j/zx - 0.5);
            if ( (m >= -ys) && (m < ys) && (n >= -xs) && (n < xs) )
                image_out[i+ys][j+xs] = image_in[m+ys][n+xs];
            else
                image_out[i+ys][j+xs] = 0;
        }
    }
}

拡大・縮小(線形補完法)

#include    "Params.h"

/*--- scale --- 拡大縮小(線形補間法)-----------------------------------------
    image_in:    入力画像配列
    image_out:    出力画像配列
    zx:            拡大率(横)
    zy:            拡大率(縦)
-----------------------------------------------------------------------------*/
void scale(unsigned char image_in[Y_SIZE][X_SIZE], 
    unsigned char image_out[Y_SIZE][X_SIZE], float zx, float zy)
{
    int    i, j, m, n;
    float    x, y, p, q;
    int    xs = X_SIZE/2;
    int    ys = Y_SIZE/2;
    int     d;

    for (i = -ys; i< ys; i++) {
        for (j = -xs; j< xs; j++) {
            y = i/zy;
            x = j/zx;
            if (y > 0) m = (int)y;
            else m = (int)(y-1);
            if (x > 0) n = (int)x;
            else n = (int)(x-1);
            q = y - m;
            p = x - n;
            if (q == 1) {q = 0; m = m + 1;}
            if (p == 1) {p = 0; n = n + 1;}
            if ( (m >= -ys) && (m < ys) && (n >= -xs) && (n < xs) )
                d = (int)((1.0-q)*((1.0-p)*image_in[m  +ys][n  +xs]
                                       + p*image_in[m  +ys][n+1+xs])
                              + q*((1.0-p)*image_in[m+1+ys][n  +xs]
                                       + p*image_in[m+1+ys][n+1+xs]));
            else
                d = 0;
            if (d <   0) d =   0;
            if (d > 255) d = 255;
            image_out[i+ys][j+xs] = d;
        }
    }
}

位置の移動

#include    "Params.h"

/*--- shift --- 位置の移動(線形補間法)---------------------------------------
    image_in:    入力画像配列
    image_out:    出力画像配列
    px:            移動量(横)
    py:            移動量(縦)
-----------------------------------------------------------------------------*/
void shift(unsigned char image_in[Y_SIZE][X_SIZE], 
    unsigned char image_out[Y_SIZE][X_SIZE], float px, float py)
{
    int    i, j, m, n;
    float    x, y, p, q;
    int    xs = X_SIZE/2;
    int    ys = Y_SIZE/2;
    int    d;

    for (i = -ys; i < ys; i++) {
        for (j = -xs; j < xs; j++) {
            y = i - py;
            x = j - px;
            if (y > 0) m = (int)y;
            else m = (int)(y-1);
            if (x > 0) n = (int)x;
            else n = (int)(x-1);
            q = y - m;
            p = x - n;
            if ( (m >= -ys) && (m < ys) && (n >= -xs) && (n < xs) )
                d = (int)((1.0-q)*((1.0-p)*image_in[m  +ys][n  +xs]
                                       + p*image_in[m  +ys][n+1+xs])
                              + q*((1.0-p)*image_in[m+1+ys][n  +xs]
                                       + p*image_in[m+1+ys][n+1+xs]));
            else
                d = 0;
            if (d <   0) d =   0;
            if (d > 255) d = 255;
            image_out[i+ys][j+xs] = d;
        }
    }
}

回転(線形補間法)

#include    <math.h>
#include    "Params.h"

/*--- rotation --- 回転(線形補間法)------------------------------------------
    image_in:    入力画像配列
    image_out:    出力画像配列
    deg:        回転角(度)
-----------------------------------------------------------------------------*/
void rotation(unsigned char image_in[Y_SIZE][X_SIZE], 
    unsigned char image_out[Y_SIZE][X_SIZE], float deg)
{
    int    i, j, m, n;
    float    x, y, p, q;
    double    r;
    float    c,s;
    int    xs = X_SIZE/2;
    int    ys = Y_SIZE/2;
    int    d;

    r = deg*3.141592/180.0;
    c = (float)cos(r);
    s = (float)sin(r);
    for (i = -ys; i < ys; i++) {
        for (j = -xs; j < xs; j++) {
            y = j*s + i*c;
            x = j*c - i*s;
            if (y > 0) m = (int)y;
            else m = (int)(y-1);
            if (x > 0) n = (int)x;
            else n = (int)(x-1);
            q = y - m;
            p = x - n;
            if ( (m >= -ys) && (m < ys) && (n >= -xs) && (n < xs) )
                d = (int)((1.0-q)*((1.0-p)*image_in[m  +ys][n  +xs]
                                       + p*image_in[m  +ys][n+1+xs])
                              + q*((1.0-p)*image_in[m+1+ys][n  +xs]
                                       + p*image_in[m+1+ys][n+1+xs]));
            else
                d = 0;
            if (d <   0) d =   0;
            if (d > 255) d = 255;
            image_out[i+ys][j+xs] = d;
        }
    }
}

拡大・縮小、回転、移動(線形補間法)

#include    <math.h>
#include    "Params.h"

//#define PI    3.141592

/*--- affine --- 拡大縮小,回転,移動(線形補間法)------------------------------
    image_in:    入力画像配列
    image_out:    出力画像配列
    deg:        回転角(度)
    zx:            拡大率(横)
    zy:            拡大率(縦)
    px:            移動量(横)
    py:            移動量(縦)
-----------------------------------------------------------------------------*/
void affine(unsigned char image_in[Y_SIZE][X_SIZE], 
    unsigned char image_out[Y_SIZE][X_SIZE], 
    float deg, float zx, float zy, float px, float py)
{
    int    i, j, m, n;
    float    x, y, u, v, p, q;
    double    r;
    float    c, s;
    int    xs = X_SIZE/2;
    int    ys = Y_SIZE/2;
    int    d;

    r = deg*PI/180.0;
    c = (float)cos(r);
    s = (float)sin(r);
    for (i = -ys; i < ys; i++) {
        for (j = -xs; j < xs; j++) {
            v = i - py;
            u = j - px;
            y = (u*s + v*c) / zy;
            x = (u*c - v*s) / zx;
            if (y > 0) m = (int)y;
            else m = (int)(y-1);
            if (x > 0) n = (int)x;
            else n = (int)(x-1);
            q = y - m;
            p = x - n;
            if ( (m >= -ys) && (m < ys) && (n >= -xs) && (n < xs) )
                d = (int)((1.0-q)*((1.0-p)*image_in[m  +ys][n  +xs]
                                       + p*image_in[m  +ys][n+1+xs])
                              + q*((1.0-p)*image_in[m+1+ys][n  +xs]
                                       + p*image_in[m+1+ys][n+1+xs]));
            else
                d = 0;
            if (d <   0) d =   0;
            if (d > 255) d = 255;
            image_out[i+ys][j+xs] = d;
        }
    }
}

透視変換(線形補間法)

#include    <math.h>
#include    "Params.h"

void param_pers(float k[9], float a, float b, float x0, 
    float y0, float z0, float z, float x, float y, float t, float s);
void matrix(double l[4][4], double m[4][4], double n[4][4]);

/*--- perspect --- 透視変換(線形補間法)--------------------------------------
    image_in:    入力画像配列
    image_out:    出力画像配列
    ax:            拡大率(横)
    ay:            拡大率(縦)
    px:            移動量(x)
    py:            移動量(y)
    pz:            移動量(z)
    rz:            回転角(z度)
    rx:            回転角(x度)
    ry:            回転角(y度)
    v:            視点の位置(z)
    s:            スクリーンの位置(z)
-----------------------------------------------------------------------------*/
void perspect(unsigned char image_in[Y_SIZE][X_SIZE], 
    unsigned char image_out[Y_SIZE][X_SIZE], float ax, float ay, 
    float px, float py, float pz, float rz, 
    float rx, float ry, float v, float s)
{
    int    i, j, m, n;
    float    x, y, w, p, q;
    float    k[9];
    int    xs = X_SIZE/2;
    int    ys = Y_SIZE/2;
    int     d;

    param_pers(k,ax,ay,px,py,pz,rz,rx,ry,v,s); /* 変換パラメータ決定 */
    for (i = -ys; i < ys; i++) {
        for (j = -xs; j < xs; j++) {
            w = k[0]*j + k[1]*i + k[2];
            x = k[3]*j + k[4]*i + k[5];
            y = k[6]*j + k[7]*i + k[8];
            x = x/w;
            y = y/w;
            if (y > 0) m = (int)y;
            else m = (int)(y-1);
            if (x > 0) n = (int)x;
            else n = (int)(x-1);
            q = y - m;
            p = x - n;
            if ( (m >= -ys) && (m < ys) && (n >= -xs) && (n < xs) )
                d = (int)((1.0-q)*((1.0-p)*image_in[m  +ys][n  +xs]
                                       + p*image_in[m  +ys][n+1+xs])
                              + q*((1.0-p)*image_in[m+1+ys][n  +xs]
                                       + p*image_in[m+1+ys][n+1+xs]));
            else
                d = 0;
                if (d <   0) d =   0;
                if (d > 255) d = 255;
                image_out[i+ys][j+xs] = d;
        }
    }
}

/*--- param_pers --- 透視変換のパラメータ計算 ---------------------------------
    k:        変換パラメータ
    a:        拡大率(x方向)
    b:        拡大率(y方向)
    x0:        移動量(x方向)
    y0:        移動量(y方向)
    z0:        移動量(z方向)
    z:        回転角(z方向,度)
    x:        回転角(x方向,度)
    y:        回転角(y方向,度)
    t:        視点の位置(z方向)
    s:        スクリーンの位置(z方向)
-----------------------------------------------------------------------------*/
void param_pers(float k[9], float a, float b, float x0, 
    float y0, float z0, float z, float x, float y, float t, float s)
{
    double    l[4][4],m[4][4],n[4][4];
    float    k1,k2,k3,k4,k5,k6,k7,k8,k9;
    double    u,v,w;
    int    xs = X_SIZE/2;
    int    ys = Y_SIZE/2;

    u=x*3.141592/180.0;    v=y*3.141592/180.0;    w=z*3.141592/180.0;
    l[0][0]= 1.0/xs;  l[0][1]= 0;       l[0][2]= 0;        l[0][3]= 0;
    l[1][0]= 0;       l[1][1]= -1.0/xs; l[1][2]= 0;        l[1][3]= 0;
    l[2][0]= 0;       l[2][1]= 0;       l[2][2]= 1;        l[2][3]= 0;
    l[3][0]= 0;       l[3][1]= 0;       l[3][2]= 0;        l[3][3]= 1;
    m[0][0]= a;       m[0][1]= 0;       m[0][2]= 0;        m[0][3]= 0;
    m[1][0]= 0;       m[1][1]= b;       m[1][2]= 0;        m[1][3]= 0;
    m[2][0]= 0;       m[2][1]= 0;       m[2][2]= 1;        m[2][3]= 0;
    m[3][0]= 0;       m[3][1]= 0;       m[3][2]= 0;        m[3][3]= 1;
    matrix(l,m,n);    /* 正規化マトリックス × 拡大縮小マトリックス */
    l[0][0]= 1;       l[0][1]= 0;       l[0][2]= 0;        l[0][3]= 0;
    l[1][0]= 0;       l[1][1]= 1;       l[1][2]= 0;        l[1][3]= 0;
    l[2][0]= 0;       l[2][1]= 0;       l[2][2]= 1;        l[2][3]= 0;
    l[3][0]= x0;      l[3][1]= y0;      l[3][2]= z0;       l[3][3]= 1;
    matrix(n,l,m);    /* × 移動マトリックス */
    n[0][0]=  cos(w); n[0][1]= sin(w);  n[0][2]= 0;        n[0][3]= 0;
    n[1][0]= -sin(w); n[1][1]= cos(w);  n[1][2]= 0;        n[1][3]= 0;
    n[2][0]= 0;       n[2][1]= 0;       n[2][2]= 1;        n[2][3]= 0;
    n[3][0]= 0;       n[3][1]= 0;       n[3][2]= 0;        n[3][3]= 1;
    matrix(m,n,l);    /* × z軸の回転マトリックス */
    m[0][0]= 1;       m[0][1]= 0;       m[0][2]= 0;       m[0][3]= 0;
    m[1][0]= 0;       m[1][1]= cos(u);  m[1][2]= sin(u);  m[1][3]= 0;
    m[2][0]= 0;       m[2][1]= -sin(u); m[2][2]= cos(u);  m[2][3]= 0;
    m[3][0]= 0;       m[3][1]= 0;       m[3][2]= 0;       m[3][3]= 1;
    matrix(l,m,n);    /* × x軸の回転マトリックス */
    l[0][0]= cos(v);  l[0][1]= 0;       l[0][2]= sin(v);  l[0][3]= 0;
    l[1][0]= 0;       l[1][1]= 1;       l[1][2]= 0;       l[1][3]= 0;
    l[2][0]= -sin(v); l[2][1]= 0;       l[2][2]= cos(v);  l[2][3]= 0;
    l[3][0]= 0;       l[3][1]= 0;       l[3][2]= 0;       l[3][3]= 1;
    matrix(n,l,m);    /* × y軸の回転マトリックス */
    n[0][0]= 1;       n[0][1]= 0;       n[0][2]= 0;       n[0][3]= 0;
    n[1][0]= 0;       n[1][1]= 1;       n[1][2]= 0;       n[1][3]= 0;
    n[2][0]= 0;       n[2][1]= 0;       n[2][2]= -1;      n[2][3]= 0;
    n[3][0]= 0;       n[3][1]= 0;       n[3][2]= t;       n[3][3]= 1;
    matrix(m,n,l);    /* × 視点座標変換マトリックス */
    m[0][0]= 1;       m[0][1]= 0;       m[0][2]= 0;       m[0][3]= 0;
    m[1][0]= 0;       m[1][1]= 1;       m[1][2]= 0;       m[1][3]= 0;
    m[2][0]= 0;       m[2][1]= 0;       m[2][2]= 1/s;     m[2][3]= 1/s;
    m[3][0]= 0;       m[3][1]= 0;       m[3][2]= -1;      m[3][3]= 0;
    matrix(l,m,n);    /* × 透視変換マトリックス */
    l[0][0]= xs;      l[0][1]= 0;       l[0][2]= 0;       l[0][3]= 0;
    l[1][0]= 0;       l[1][1]= -xs;     l[1][2]= 0;       l[1][3]= 0;
    l[2][0]= 0;       l[2][1]= 0;       l[2][2]= 1;       l[2][3]= 0;
    l[3][0]= 0;       l[3][1]= 0;       l[3][2]= 0;       l[3][3]= 1;
    matrix(n,l,m);    /* × 正規化逆マトリックス */
    k1=(float)(m[0][3]);    k2=(float)(m[1][3]);    k3=(float)(m[3][3]);
    k4=(float)(m[0][0]);    k5=(float)(m[1][0]);    k6=(float)(m[3][0]);
    k7=(float)(m[0][1]);    k8=(float)(m[1][1]);    k9=(float)(m[3][1]);
    k[0]=k7*k2-k8*k1; k[1]=k5*k1-k4*k2; k[2]=k4*k8-k7*k5;
    k[3]=k8*k3-k9*k2; k[6]=k9*k1-k7*k3; k[4]=k6*k2-k5*k3;
    k[7]=k4*k3-k6*k1; k[5]=k5*k9-k8*k6; k[8]=k7*k6-k4*k9;
}

/*--- matrix --- マトリックス計算 ---------------------------------------------
    l:    入力マトリックス1
    m:    入力マトリックス2
    n:    出力マトリックス
-----------------------------------------------------------------------------*/
void matrix(double l[4][4], double m[4][4], double n[4][4])
{
    int        i, j, k;
    double    p;

    for (i = 0; i < 4; i++) {
        for (j = 0; j < 4; j++) {
            p = 0;
            for (k = 0; k < 4; k++) p = p + l[i][k]*m[k][j];
            n[i][j] = p;
        }
    }
}