第4章 輪郭を抜き出す
輪郭について
輪郭について下図を用いて説明する。
- (a)エッジ
- 急激なステップ状の変化を表す。典型的な輪郭のパターンである。
はっきりとした輪郭に見えるので、これを特に”エッジ”と呼ぶ。
- (b)線
- 線そのものの濃度変化で、これも輪郭に見える。
線状の物体がある場合や照明の具合でその物体の影が付いた場合に生じる。
- (c)折れ線状の変化
- 濃度値が折れ線状に変化したもの。
(a),(b)ほどはっきりしたものには
ならないが、折れ線の角度が急になれば輪郭に見える。
- (d)緩やかで滑らかな変化
- 濃度の変化はあるが緩やかに変化しているため、輪郭には見えない。
様々な輪郭抽出法とその例
標準画像Lenna.bmpに対して、様々な輪郭抽出方法を試した例とそのオペレータを示す。
リスト4.1〜4.3の倍数(amp)は全て5に設定した。
なお、この値はテキストでは利得と表現しているが、閾値の大きさを調整するものである。
1次微分(Gradient), 通常の差分
x方向、y方向の微分(差分)を取ることにより、
x, y方向の輪郭を抽出する。
1次微分は、ここでは、x, y方向の差分値のユークリッドノルムまたは絶対値ノルムで表される。
1次微分(Gradient), Robertsオペレータ
斜め方向の差分を取って、上と同様の処理を行う。斜め方向のエッジ抽出に向いている。
1次微分(Gradient), Sobelオペレータ
縦横、斜めの差分を取るが、縦と横のエッジを強調している。
2次微分(Laplacian), 4方向
上下左右4方向に2次微分することにより強さを求める。方向性に依存しない。
2次微分(Laplacian), 8方向
オペレータ
-1 | -1 | -1 |
-1 | 8 | -1 |
-1 | -1 | -1 |
4方向のものに斜め方向にも2次微分したものである。
2次微分(Laplacian), 8方向(2)
上のパラメータの重み付けを変えたものである。
Prewittのテンプレートマッチング, 8方向
8つのマスクごとに計算し、最大の差分値をエッジの強さとする方法である。
輪郭抽出例
具体的な画像データに対して、一次微分を用いて輪郭を抜き出す例を示す。
はじめに、説明した後にプログラムを示す。
3章で作成した2値化のプログラムと、
ヒストグラムファイル出力のプログラムを用いて、二値化する 。
はじめに、Lenna.bmpに対して(4.1), (4.2)式を適用する。ただし、Robertsオペレータを
用いる。
適用して得られる値は、0〜255*sqrt(2)の範囲を取るので、255を越えた値は255とする。
この結果を次に示す。
この画像を二値化するのに、閾値の値を次のようにヒストグラムから求める。
ヒストグラム
ヒストグラムは上のようになった。
このグラフを見ながら、閾値を与えた例を次に示す。
二値画像例
左から順番に、閾値40, 150, 250である。
細線化例
上の閾値150で二値化した画像に対して、Hilditchの細線化処理を施した。
プログラミング例
1次微分
#include <math.h>
#include "Params.h"
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,
0, 1, 0,
0, 0,-1};
static int cy[9] = { 0, 0, 0,
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"
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
-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"
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] );
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;
}
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;
}