第3章 物体を抜き出す

  1. 閾値処理例
  2. 閾値を決定する
  3. プログラミング例

閾値処理例

元画像
上の画像に対して閾値(しきいち)処理をして、文字の部分のみ 抽出(抜き出すこと)方法を以下に説明する。他の成書では、二値化処理と称することが多い。
この画像のBMP形式をダウンロードして展開すると、e-img.bmpが 得られる。このデータに対して、閾値処理を行う例を示す。

画像処理において、抽出した文字を白く表示したい。ここでは、256階調のグレースケールで 表現しているので、白はHIGH(255)、黒はLOW(0)である。
文字を抽出するために、(3.2)式を利用する。ここに、抽出したい文字は黒であるから、 設定した閾値より小さい画素値ならば、この値を抽出し、論理的にHIGHと置く。 論理的に考えるならば、HIGHは1でよい。しかし、実際の画像表示に都合がよいよう255を あてている。
以下に、いろいろな閾値に対する二値化処理結果を示す。

閾値40

40

閾値80

80

閾値120

120

閾値160

160

閾値200

200

閾値を決定する

本書では閾値の定め方として、ヒストグラム(histgram)を用いる。
ここでのヒストグラムとは画像の各濃度値の分布をグラフで表したものである。
ここでは、各階調のpixelの頻度をテキスト形式でファイルに出力する関数 を作成し、出力したファイルに対してGnuplotを用いてヒストグラムのグラフ表示する。
(本書のサンプルだとpixel数に合わせて幅を調整せねばならないが、 gnuplotを使えばその考慮は不要である。)

histgram.txtを出力した。このファイルの0〜255の範囲を見るために、 x軸の範囲を少し余分に取るよう、 以下のようにコマンドを入力した。

gnuplot> set xlabel "Brightness"
gnuplot> set ylabel "Number of pixels"
gnuplot> set xrange[-10:265]
gnuplot> plot "histgram.txt" w i

プロット
このままだと、谷を見つけにくいので、前後2画素との平均を取る関数を 作成し、これを利用した結果が下のグラフである。

平均化してプロット
先のものに比べヒストグラムの包絡線が滑らかになり、グラフの山と谷が見やすくなった。
このサンプル画像は白い背景の部分のpixelの方が多いので、 文字と背景の境目は50〜100の谷であろうとみなして、閾値を85に設定した結果が次である。

閾値85

プログラミング例

閾値処理

#include "Params.h"
/*--- threshold --- 閾(しきい)値処理 ----------------------------------------
    image_in:    入力画像配列
    image_out:    出力画像配列
    thresh:        閾値
    mode:        閾値処理の方法(1,2)
-----------------------------------------------------------------------------*/
void threshold(unsigned char image_in[Y_SIZE][X_SIZE], 
               unsigned char image_out[Y_SIZE][X_SIZE], int thresh, int mode)
{
    int i,j;
    
    for (i = 0; i < Y_SIZE; i++) {
        for ( j = 0; j < X_SIZE; j++) {
            switch (mode) {
            case 2:
                if ((int)image_in[i][j] <= thresh) image_out[i][j] = HIGH;
                else                               image_out[i][j] =  LOW;
                break;
            default:
                if ((int)image_in[i][j] >= thresh) image_out[i][j] = HIGH;
                else                              image_out[i][j] =  LOW;
                break;
            }
        }
    }
}

ヒストグラムを求める

#include "Params.h"
/*--- histgram --- ヒストグラムを求める処理 -----------------------------------
    image_in:    入力画像配列
    hist:        ヒストグラム
-----------------------------------------------------------------------------*/
void histgram(unsigned char image_in[Y_SIZE][X_SIZE], long hist[256])
{
    int i,j,n;

    for (n = 0; n < 256; n++) hist[n] = 0;
    for (i = 0; i < Y_SIZE; i++) {
        for ( j = 0; j < X_SIZE; j++) {
            n = image_in[i][j];
            hist[n]++;
        }
    }
}

ヒストグラムをファイル出力する

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

/************************************************************
 outputHistgram : 256段階のヒストグラムファイルを出力する関数
 ***********************************************************/
void outputHistgram(long histgram[256]){
    FILE *filePointer;
    int c;
    char fileName[40];
    
    printf("Input histgram text file name: ");
    scanf("%s",&fileName);
    
    //output text file
    filePointer = fopen(fileName, "w");
    if(filePointer != NULL){
         for(c=0; c<256; c++){
             fprintf(filePointer, "%d\t%d\n",c,histgram[c]);
         }
        fclose(filePointer);
        printf("%s was Outputed.\n", fileName);
    }else{
        printf("Error.\n");
    }
}

ヒストグラムを平均化する

#include "Params.h"
/*--- histsmooth --- ヒストグラムを平滑化する ---------------------------------
    hist_in:    ヒストグラム 平滑化前
    hist_out:    ヒストグラム 平滑化後
-----------------------------------------------------------------------------*/
void histsmooth(long hist_in[256], long hist_out[256])
{
    int  m, n, i;
    long sum;

    for (n = 0; n < 256; n++) {
    sum = 0;
    for (m = -2; m <= 2; m++) {
        i = n + m;
        if (i <   0) i =   0;
        if (i > 255) i = 255;
        sum = sum + hist_in[i];
    }
    hist_out[n] = (long)((float)sum / 5.0 + 0.5);
    }
}