[BlueLeaf1336]> PROBLEMS> なぁバーコードを読もうじゃないか>

なぁバーコードを読もうじゃないか > バーコードらしさ

historyTOP

2005/04/07:作成
2005/04/13:更新
2005/04/14:リンク追加
2005/04/30:別の方法

2005/04/07TOP

今までに、CODE-39とJANコードが、異常に甘やかされた環境下で読めるようになりました。しかし実際には、少なくとも直立していて欲しいという条件は譲れなくても、背景にゴミがどっさりある状態でバーコードだけを抜き出したいわけです。

具体的には、何らかのバーコードの含まれる画像があって、その中からバーコードらしき領域を切り出す。それさえできれば、それが実際にバーコードなのかどうかを判定できることになります。

ここで問題になるのは、バーコードらしさとは何か、ということですが、1次元バーコードに限っていうと、「平行な線分が結構たくさん並んでいる」という条件でよいような気がします。いや正直に言えば、

こんなページを見つけたので(中も読まずに)、直線が抜き出せるんやん。じゃあ出来るんちゃん。と早とちりです。そういうわけで、画像から直線を抜き出し、一定数以上の平行と思われる直線を見つけた日にゃあ、それをバーコードとみなしてやろうというのを、やってみることにします。嫌になったらすぐに手を引きます。

2005/04/13TOP

まず結果からいうと、駄目みたいです。終り...の前に、上記のリンクを参考にほぼ理解せずに直訳をやっただけですが、二値化・エッジ検出・Hough変換による直線検出を試しました。特に最後のHough変換については参考にさせていただいたページに謝らなければならないぐらいな感じがします。

テストに使用した画像は、下記のサイトからもらいました。

で、コレが結果です。何故バーコードの入った画像を使用しないかについては気分の問題です。ただ、本当にバーコードしかない画像で直線の検出をすると、予想以上にきっちりと直線を検出していました。

まず元の画像。

これを二値化したもの。

またエッジの検出をしたもの。

エッジの検出をした画像で10本の直線の検出したもの。

で、実行ファイルとソースコードです。

20050413ForNaBaRead.zip(175,677bytes)

Hough変換を理解できていないし、参考にしたサイトのせっかくの説明も全く読んでいないのですが、検出したい本数を指定する必要があるのと、応用できるほど理解できていないのと、よくよく考えるとバーコード1本1本ではなくて全体を捕まえないと駄目なことから、この方向は諦めることにします。

ただ、二値化とエッジ検出は使えるような気がします。

白黒二値化TOP

//-----------------------------------------------------------------------------
//  白黒二値化
//  http://homepage3.nifty.com/ishidate/vcpp6_g8/vcpp6_g8.htm
procedure Monochromize(Src, Dst: TBitmap; Threshold: integer = 180);
var
    i, j: integer;
    LineS, LineD: PByteArray;
    R, G, B, Gray: Byte;
    Check: integer;
begin
    Dst.Width := Src.Width;
    Dst.Height := Dst.Height;

    for i := 0 to Src.Height - 1 do
    begin
        //  1行メモリ内容
        LineS := Src.ScanLine[i];
        LineD := Dst.ScanLine[i];

        for j := 0 to Src.Width - 1 do
        begin
            //  RGB値をそれぞれ取り出して
            R := LineS[j * 3 + 2];
            G := LineS[j * 3 + 1];
            B := LineS[j * 3 + 0];

            //  白黒判定
            Check := 2 * R + 4 * G + B;
            Gray := 0;
            if (Check > Threshold * 7) then Gray := 255;

            //  RGB値をそれぞれ書き込む
            LineD[j * 3 + 2] := Gray;
            LineD[j * 3 + 1] := Gray;
            LineD[j * 3 + 0] := Gray;
        end;
    end;
end;

この関数を次のように使用します。1つ目に引数に二値化したい画像を読み込んだTBitmapを、2つ目に結果を格納するTBitmapを指定します。

    //  原画像の白黒二値化
    Monochromize(Image1.Picture.Bitmap, Bmp);

エッジ検出TOP

uses
    Math;

//-----------------------------------------------------------------------------
//  画像を上下左右に1ピクセル拡張する(上下で2ピクセル/左右で2ピクセル)
//  拡張したピクセルには元画像の端っこをコピーする(左拡張箇所には左端をコピー)
procedure Expand(Bmp: TBitmap);
var
    SrcW, SrcH: integer;
begin
    SrcW := Bmp.Width;
    SrcH := Bmp.Height;

    //  左右の幅を広げる
    Bmp.Width := SrcW + 2;

    //  上下の幅を広げる
    Bmp.Height := SrcH + 2;

    //  右下に1ピクセルずらす
    BitBlt(Bmp.Canvas.Handle, 1, 1, SrcW, SrcH, Bmp.Canvas.Handle,
           0, 0, SRCCOPY);

    //  ずれて空白になった部分を埋めていく

    //  左端
    BitBlt(Bmp.Canvas.Handle, 0, 0, 1, Bmp.Height,
        Bmp.Canvas.Handle, 1, 0, SRCCOPY);

    //  右端
    BitBlt(Bmp.Canvas.Handle, Bmp.Width - 1, 0, 1, Bmp.Height,
        Bmp.Canvas.Handle, Bmp.Width - 2, 0, SRCCOPY);

    //  上端
    BitBlt(Bmp.Canvas.Handle, 0, 0, Bmp.Width, 1,
        Bmp.Canvas.Handle, 0, 1, SRCCOPY);

    //  下端
    BitBlt(Bmp.Canvas.Handle, 0, Bmp.Height - 1, Bmp.Width, 1,
        Bmp.Canvas.Handle, 0, Bmp.Height - 2, SRCCOPY);

    //  左上隅
    Bmp.Canvas.Pixels[0, 0] := Bmp.Canvas.Pixels[1, 1];

    //  右上隅
    Bmp.Canvas.Pixels[Bmp.Width - 1, 0] := Bmp.Canvas.Pixels[Bmp.Width - 2, 1];

    //  左下隅
    Bmp.Canvas.Pixels[0, Bmp.Height - 1] := Bmp.Canvas.Pixels[1, Bmp.Height - 2];

    //  右下隅
    Bmp.Canvas.Pixels[Bmp.Width - 1, Bmp.Height - 1] := Bmp.Canvas.Pixels[Bmp.Width - 2, Bmp.Height - 2];
end;

//-----------------------------------------------------------------------------
//  Edge検出のためのOperatorの設定
type
    TOperator = array[0..2, 0..2] of Integer;

const
    Roberts: TOperator = ((0,0,0), (0,1,0), (-1,0,0));
    Prewitt: TOperator = ((1,1,1), (0,0,0), (-1,-1,-1));
    Sobel  : TOperator = ((1,2,1), (0,0,0), (-1,-2,-1));

//-----------------------------------------------------------------------------
//  Edgeの強さの最大値を求める
type
    TWordWordArray = array of array of Word;

function MaxValue(const WwArray: TWordWordArray): Word;
var
    i, j: integer;
begin
    Result := 0;
    for i := 0 to Length(WwArray) - 1 do
    begin
        for j := 0 to Length(WwArray[i]) - 1 do
        begin
            if WwArray[i][j] > Result then Result := WwArray[i][j];
        end;
    end;
end;

//-----------------------------------------------------------------------------
//  Edge検出
procedure RetrieveEdge(Src, Dst: TBitmap; const Ope: TOperator);
var
    i, j, k, l: integer;
    LineS: array[0..2] of PByteArray;
    LineD: PByteArray;
    R, G, B, Edge: Byte;
    R1, G1, B1: integer;
    R2, G2, B2: integer;
    V, H: integer;
    wEdge: TWordWordArray;
    wMax: Word;
begin
    Dst.Width := Src.Width;
    Dst.Height := Dst.Height;

    //  作業用配列のサイズを決定
    SetLength(wEdge, Src.Height, Src.Width);

    for i := 1 to Src.Height - 2 do
    begin
        for j := 1 to Src.Width - 2 do
        begin

            R1 := 0;
            G1 := 0;
            B1 := 0;
            R2 := 0;
            G2 := 0;
            B2 := 0;

            //  1行メモリ内容
            LineS[0] := Src.ScanLine[i + 0 - 1];
            LineS[1] := Src.ScanLine[i + 1 - 1];
            LineS[2] := Src.ScanLine[i + 2 - 1];

            for k := 0 to 2 do
            begin
                for l := 0 to 2 do
                begin
                    //  RGB値をそれぞれ取り出して
                    R := LineS[l][(j + k - 1) * 3 + 2];
                    G := LineS[l][(j + k - 1) * 3 + 1];
                    B := LineS[l][(j + k - 1) * 3 + 0];

                    //  横方向の変化検出
                    Inc(R1, R * Ope[l, k]);
                    Inc(G1, G * Ope[l, k]);
                    Inc(B1, B * Ope[l, k]);

                    //  縦方向の変化検出
                    Inc(R2, R * Ope[k, l]);
                    Inc(G2, G * Ope[k, l]);
                    Inc(B2, B * Ope[k, l]);
                end;
            end;

            V := Abs(R1) + Abs(G1) + Abs(B1);
            H := Abs(R2) + Abs(G2) + Abs(B2);

            //  大きい方を採用
            if (V >= H) then wEdge[i, j] := V else wEdge[i, j] := H;
        end;
    end;

    //  Edgeの強さの最大値を求める
    wMax := MaxValue(wEdge);

    //  Edgeの強さの最大値の1/8を閾値にしてEdgeを描画する
    for i := 0 to Src.Height - 1 do
    begin
        //  1行メモリ内容
        LineD := Dst.ScanLine[i];

        for j := 0 to Src.Width - 1 do
        begin
            if (wEdge[i, j] * 8 > wMax) then Edge := 0 else Edge := 255;

            //  RGB値をそれぞれ書き込む
            LineD[j * 3 + 2] := Edge;
            LineD[j * 3 + 1] := Edge;
            LineD[j * 3 + 0] := Edge;
        end;
    end;
end;

これを次のように使います。注意点としては、使いにくいことぐらいです。

    //  オリジナルを読み込んで
    Bmp.Assign(Image1.Picture.Bitmap);

    //  画像を拡張
    Expand(Bmp);

    //  Edge検出
    //RetrieveEdge(Bmp, Bmp, Roberts);
    RetrieveEdge(Bmp, Bmp, Prewitt);
    //RetrieveEdge(Bmp, Bmp, Sobel);

詳細は、参考サイトを見るのが一番ですが、1つ目で処理したい画像をコピーしています。これはエッジ検出時に処理する画像を上下左右に1ピクセルずつ拡大したいので、TImage.Picture.Bitmapを直接やるのもアレなんで、TBitmapに一旦渡しているだけです。次に、画像を拡大します。3x3の行列を各ピクセルに当てはめていく(?)時に、端っこのピクセルも同じように扱いたいのではみ出てしまう分の受け皿を最初に作っておくようです。そして最後にエッジの検出をやっています。別に3つに分ける必要はないんですが、CをDelphiに翻訳するときに一塊ずつやった名残です。

いつか自分の役に立ちますように。さて、バーコードらしき領域を認識するための別の方法を考える必要が出てきました。いかにもあてがありそうな書きかたですが、全くコレッポッチもあてはないです。

2005/04/14TOP

テンプレートマッチングというコトバが最近気になり始めています。

2005/04/30TOP

異常に難しい。テンプレートマッチング。というわけでやめました。全然かわりになっていませんが、今まで横一直線を丸ごとバーコードかどうか調べていた処理を、矩形を指定することでその範囲内だけ調べるように変更しました。屁のツッパリにもなりませんが。それから、判定結果をコピーできるようにエディットボックスに書き出すようにしました。コピーなんかしませんけど。

20050430NaBaRead.zip(297,751bytes)

EOFTOP