第4章 輪郭を抜き出す

輪郭について

輪郭について下図を用いて説明する。
輪郭の濃度変化モデル

(a)エッジ
急激なステップ状の変化を表す。典型的な輪郭のパターンである。
はっきりとした輪郭に見えるので、これを特に”エッジ”と呼ぶ。
(b)線
線そのものの濃度変化で、これも輪郭に見える。
線状の物体がある場合や照明の具合でその物体の影が付いた場合に生じる。
(c)折れ線状の変化
濃度値が折れ線状に変化したもの。
(a),(b)ほどはっきりしたものには ならないが、折れ線の角度が急になれば輪郭に見える。
(d)緩やかで滑らかな変化
濃度の変化はあるが緩やかに変化しているため、輪郭には見えない。

様々な輪郭抽出法とその例

lenna
標準画像Lenna.bmpに対して、様々な輪郭抽出方法を試した例とそのオペレータを示す。
リスト4.1〜4.3の倍数(amp)は全て5に設定した。 なお、この値はテキストでは利得と表現しているが、閾値の大きさを調整するものである。

1次微分(Gradient), 通常の差分

y方向のオペレータ
000
010
0-10
x方向のオペレータ
000
01-1
000

gradient
x方向、y方向の微分(差分)を取ることにより、 x, y方向の輪郭を抽出する。
1次微分は、ここでは、x, y方向の差分値のユークリッドノルムまたは絶対値ノルムで表される。

1次微分(Gradient), Robertsオペレータ

y方向のオペレータ
000
001
0-10
x方向のオペレータ
000
010
00-1

Roberts
斜め方向の差分を取って、上と同様の処理を行う。斜め方向のエッジ抽出に向いている。

1次微分(Gradient), Sobelオペレータ

x方向のオペレータ
-1-2-1
000
121
y方向のオペレータ
-101
-202
-101

Sobel
縦横、斜めの差分を取るが、縦と横のエッジを強調している。

2次微分(Laplacian), 4方向

オペレータ
0-10
-14-1
0-10

Laplacian 4方向
上下左右4方向に2次微分することにより強さを求める。方向性に依存しない。

2次微分(Laplacian), 8方向

オペレータ
-1-1-1
-18-1
-1-1-1

Laplacian 8方向
4方向のものに斜め方向にも2次微分したものである。

2次微分(Laplacian), 8方向(2)

オペレータ
1-21
-24-2
1-21

Laplacian 8方向(2)
上のパラメータの重み付けを変えたものである。

Prewittのテンプレートマッチング, 8方向

Prewitt

マスク群
対応するエッジ 対応するエッジ 対応するエッジ 対応するエッジ
111
1-21
-1-1-1
111
1-2-1
1-1-1
11-1
1-2-1
11-1
1-1-1
1-2-1
111
対応するエッジ 対応するエッジ 対応するエッジ 対応するエッジ
-1-1-1
1-21
111
-1-11
-1-21
111
-111
-1-21
-111
111
-1-21
-1-11

8つのマスクごとに計算し、最大の差分値をエッジの強さとする方法である。

輪郭抽出例

具体的な画像データに対して、一次微分を用いて輪郭を抜き出す例を示す。 はじめに、説明した後にプログラムを示す。
3章で作成した2値化のプログラムと、 ヒストグラムファイル出力のプログラムを用いて、二値化する 。
はじめに、Lenna.bmpに対して(4.1), (4.2)式を適用する。ただし、Robertsオペレータを 用いる。 適用して得られる値は、0〜255*sqrt(2)の範囲を取るので、255を越えた値は255とする。 この結果を次に示す。

Roberts
この画像を二値化するのに、閾値の値を次のようにヒストグラムから求める。

ヒストグラム

ヒストグラム
ヒストグラムは上のようになった。
このグラフを見ながら、閾値を与えた例を次に示す。

二値画像例

閾値40 閾値150 閾値250
左から順番に、閾値40, 150, 250である。

細線化例

細線化例
上の閾値150で二値化した画像に対して、Hilditchの細線化処理を施した。

プログラミング例

1次微分

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

/*--- gradient --- 1次微分による輪郭抽出 -------------------------------------
    image_in:    入力画像配列
    image_out:    出力画像配列
    amp:        出力画像の利得
-----------------------------------------------------------------------------*/
void gradient(unsigned char image_in[Y_SIZE][X_SIZE], 
    unsigned char image_out[Y_SIZE][X_SIZE], float amp)
{
    static int cx[9] = { 0, 0, 0,    /* オペレータの係数x(Roberts) */
                         0, 1, 0,    /* 他のオペレータの時は        */
                         0, 0,-1};    /* 書き換えて下さい            */
    static int cy[9] = { 0, 0, 0,    /* オペレータの係数y(Roberts) */
                         0, 0, 1,    /* 他のオペレータの時は        */
                         0,-1, 0};    /* 書き換えて下さい            */
    int        d[9];
    int        i, j, dat;
    float    xx, yy, zz;

    for (i = 1; i < Y_SIZE-1; i++) {
        for (j = 1; j < X_SIZE-1; j++) {
            d[0] = image_in[i-1][j-1];
            d[1] = image_in[i-1][j];
            d[2] = image_in[i-1][j+1];
            d[3] = image_in[i][j-1];
            d[4] = image_in[i][j];
            d[5] = image_in[i][j+1];
            d[6] = image_in[i+1][j-1];
            d[7] = image_in[i+1][j];
            d[8] = image_in[i+1][j+1];
            xx = (float)(cx[0]*d[0] + cx[1]*d[1] + cx[2]*d[2]
                       + cx[3]*d[3] + cx[4]*d[4] + cx[5]*d[5]
                       + cx[6]*d[6] + cx[7]*d[7] + cx[8]*d[8]);
            yy = (float)(cy[0]*d[0] + cy[1]*d[1] + cy[2]*d[2]
                       + cy[3]*d[3] + cy[4]*d[4] + cy[5]*d[5]
                       + cy[6]*d[6] + cy[7]*d[7] + cy[8]*d[8]);
            zz = (float)(amp*sqrt(xx*xx+yy*yy));
            dat = (int)zz;
            if(dat > 255) dat = 255;
            image_out[i][j] = (char)dat;
        }
    }
}

2次微分

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

/*--- laplacian --- 2次微分による輪郭抽出 ------------------------------------
    image_in:    入力画像配列
    image_out:    出力画像配列
    amp:        出力画像の利得
-----------------------------------------------------------------------------*/
void laplacian(unsigned char image_in[Y_SIZE][X_SIZE], 
        unsigned char image_out[Y_SIZE][X_SIZE], float amp)
{
    static int c[9] = {-1, -1, -1    /* オペレータの係数(laplacian) */
                       -1,  8, -1    /* 他のオペレータ使用時は      */
                       -1, -1, -1};    /* 書き換えて下さい            */
    int    d[9];
    int    i, j, dat;
    float    z, zz;

    for (i = 1; i < Y_SIZE-1; i++) {
        for (j = 1; j < X_SIZE-1; j++) {
            d[0] = image_in[i-1][j-1];
            d[1] = image_in[i-1][j];
            d[2] = image_in[i-1][j+1];
            d[3] = image_in[i][j-1];
            d[4] = image_in[i][j];
            d[5] = image_in[i][j+1];
            d[6] = image_in[i+1][j-1];
            d[7] = image_in[i+1][j];
            d[8] = image_in[i+1][j+1];
            z =  (float)(c[0]*d[0] + c[1]*d[1] + c[2]*d[2]
                       + c[3]*d[3] + c[4]*d[4] + c[5]*d[5]
                       + c[6]*d[6] + c[7]*d[7] + c[8]*d[8]);
            zz = amp*z;
            dat = (int)(zz);
            if (dat <   0) dat = -dat;
            if (dat > 255) dat =  255;
            image_out[i][j] = (char)dat;
        }
    }
}

Prewittの方法による輪郭抽出

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

/*--- template --- Prewitt???@?????s??? --------------------------------
    image_in:    入力画像配列
    image_out:    出力画像配列
    amp:        出力画像の利得
-----------------------------------------------------------------------------*/
//template is YOYAKUGO!
void prewittTemplate(unsigned char image_in[Y_SIZE][X_SIZE], 
        unsigned char image_out[Y_SIZE][X_SIZE], float amp)
{
    int    d0,d1,d2,d3,d4,d5,d6,d7,d8;
    int    i,j,k,max,dat;
    int    m[8];
    float    zz;

    for (i = 1; i < Y_SIZE-1; i++) {
        for (j = 1; j < X_SIZE-1; j++) {
        d0 = image_in[i-1][j-1];
        d1 = image_in[i-1][j];
        d2 = image_in[i-1][j+1];
        d3 = image_in[i][j-1];
        d4 = image_in[i][j];
        d5 = image_in[i][j+1];
        d6 = image_in[i+1][j-1];
        d7 = image_in[i+1][j];
        d8 = image_in[i+1][j+1];
            m[0] =  d0 + d1 + d2 + d3 -2*d4 + d5 - d6 - d7 - d8;
            m[1] =  d0 + d1 + d2 + d3 -2*d4 - d5 + d6 - d7 - d8;
            m[2] =  d0 + d1 - d2 + d3 -2*d4 - d5 + d6 + d7 - d8;
            m[3] =  d0 - d1 - d2 + d3 -2*d4 - d5 + d6 + d7 + d8;
            m[4] = -d0 - d1 - d2 + d3 -2*d4 + d5 + d6 + d7 + d8;
            m[5] = -d0 - d1 + d2 - d3 -2*d4 + d5 + d6 + d7 + d8;
            m[6] = -d0 + d1 + d2 - d3 -2*d4 + d5 - d6 + d7 + d8;
            m[7] =  d0 + d1 + d2 - d3 -2*d4 + d5 - d6 - d7 + d8;
        max = 0;
        for (k = 0; k < 8; k++) if (max < m[k]) max = m[k];
            zz = amp*(float)(max);
        dat = (int)(zz);
        if (dat > 255) dat = 255;
        image_out[i][j] = (char)dat;
        }
    }
}

Hilditchの方法による二値画像の細線化

#include "Params.h"

int    cconc(int inb[9] );

/*--- thinning --- 2値画像を細線化する ---------------------------------------
    image_in:    入力画像配列
    image_out:    出力画像配列
-----------------------------------------------------------------------------*/
void thinning(unsigned char image_in[Y_SIZE][X_SIZE], 
    unsigned char image_out[Y_SIZE][X_SIZE])
{
    int    ia[9], ic[9], i, ix, iy, m, ir, iv, iw;

    for (iy = 0; iy < Y_SIZE; iy++)
        for (ix = 0; ix < X_SIZE; ix++)
            image_out[iy][ix] = image_in[iy][ix];
    m = 100; ir = 1 ;
    while (ir != 0) {
        ir = 0;
        for (iy = 1; iy < Y_SIZE-1; iy++)
            for (ix = 1; ix < X_SIZE-1; ix++) {
                if (image_out[iy][ix] != HIGH) continue;
                ia[0] = image_out[iy  ][ix+1];
                  ia[1] = image_out[iy-1][ix+1];
                ia[2] = image_out[iy-1][ix  ];
                  ia[3] = image_out[iy-1][ix-1];
                  ia[4] = image_out[iy  ][ix-1];
                  ia[5] = image_out[iy+1][ix-1];
                  ia[6] = image_out[iy+1][ix  ];
                  ia[7] = image_out[iy+1][ix+1];
                for (i = 0; i < 8; i++) {
                    if (ia[i] == m) {
                        ia[i] = HIGH; ic[i] = 0;
                    }
                    else {
                        if (ia[i] < HIGH) ia[i] = 0;
                        ic[i] = ia[i];
                    }
                }
                ia[8] = ia[0]; ic[8] = ic[0];
                if (ia[0]+ia[2]+ia[4]+ia[6] == HIGH*4) continue;
                for (i = 0, iv = 0, iw = 0; i < 8; i++) {
                    if (ia[i] == HIGH) iv++;
                    if (ic[i] == HIGH) iw++;
                }
                if (iv <= 1) continue;
                if (iw == 0) continue;
                if (cconc(ia) != 1) continue;
                  if (image_out[iy-1][ix] == m) {
                    ia[2] = 0;
                    if (cconc(ia) != 1) continue;
                    ia[2] = HIGH;
                }
                if (image_out[iy][ix-1] == m) {
                    ia[4] = 0;
                    if (cconc(ia) != 1) continue;
                    ia[4] = HIGH;
                }
                image_out[iy][ix] = m; ir++;
            }
        m++;
    }
    for (iy = 0; iy < Y_SIZE; iy++)
        for (ix = 0; ix < X_SIZE; ix++)
            if (image_out[iy][ix] < HIGH) image_out[iy][ix] = 0;
}

/*--- cconc --- 連結性(連結数)を調べる --------------------------------------
    inb:    入力画素
-----------------------------------------------------------------------------*/
int    cconc(int inb[9])
{          
    int    i, icn;
    icn = 0;

    for (i = 0; i < 8; i += 2)
        if (inb[i] == 0)
        if (inb[i+1] == HIGH || inb[i+2] == HIGH)
        icn++;
        return icn;
}