LoginSignup
3
2

More than 1 year has passed since last update.

画像補正で一次元バーコードの認識率を向上させる

Last updated at Posted at 2022-04-27

※アカウント統合により過去記事再掲:元の投稿日 2016年03月03日

バーコード読み取りにZxingを使っている人は多いと思う。

非常に認識率の高いライブラリなのだが、しかし画像読み取りの解像度や明るさによっては、微妙に読めたり読めなかったり、全く読めなかったりと認識率が安定しなくなってしまう。

画像補正でなんとか認識率を上げてみようではないか、と。そういう試みなのである。

まずは均等化してみる

白と黒を定義する

均等化するために必要なのが、対象画像の「黒」と「白」を定義する事だ。
最初は最小の平均RGB値を持つものを黒、最大を白としていたが、これだとなんか暗い値はいいけど明るい値は255周辺しか検出しない……。スキャンした画像なんか、どんな画像でも1ピクセルくらいほぼ真っ白のピクセルが混ざってたりするのか。そういうもんなのかね。
じゃあどうしようか。平均値でいいか。

平均RGB値の最小ピクセルを「黒」
平均RGB値の128以上のピクセルの、平均値を「白」

そんな感じで「黒」と「白」を定義してみたのであった。

「黒」を0に、「白」を255に揃える

こうなると、例えば暗い画像だと「黒」は0だが「白」は211だったり、明るい画像だと「黒」が119で「白」が255と、そこそこそれっぽい数値が出た。

まず暗い方の画像。これは0~211なので、211階調のグレイスケールに変換しちゃう。えいや。
org1.jpg

org1.s.jpg

お。綺麗になったじゃん。
明るい方の画像も同じ理屈で均等化しちゃう。

org6.jpg

org6.s.jpg

くっきりしてきたぞ。
いいね、いいね。

くっきり化
internal static Bitmap _smooth(Bitmap img)
{
    int width = img.Width;
    int height = img.Height;

    // 出力用のビットマップ
    var dstImg = new Bitmap(width, height);

    // ピクセルに直接アクセスしにいく
    var bmpread = img.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
    var bmpwrite = dstImg.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);

    int blackness = 255;
    int whiteness = -1;

    for (int i = 0; i < (width * height * 3); )
    {
        var r = Marshal.ReadByte(bmpread.Scan0, i++);
        var g = Marshal.ReadByte(bmpread.Scan0, i++);
        var b = Marshal.ReadByte(bmpread.Scan0, i++);

        int ave = (int)new List<int> { r, g, b }.Average();

        // 最も暗い色を抜き出して「黒」を定義
        blackness = Math.Min(blackness, ave);

        // 128以上の値を平均して「白」を定義
        if (ave >= 128)
        {
            if (whiteness == -1)
                whiteness = ave;
            else
                whiteness = (whiteness + ave) / 2;
        }
    }

    var step = 255d / ((double)(whiteness - blackness));

    for (int i = 0, j = 0; i < (width * height * 3); )
    {
        var r = Marshal.ReadByte(bmpread.Scan0, i++);
        var g = Marshal.ReadByte(bmpread.Scan0, i++);
        var b = Marshal.ReadByte(bmpread.Scan0, i++);

        var v = new List<int> { r, g, b }.Average();

        v -= blackness;

        v *= step;

        v = Math.Min(255, Math.Max(0, v));

        Marshal.WriteByte(bmpwrite.Scan0, j++, (byte)v);
        Marshal.WriteByte(bmpwrite.Scan0, j++, (byte)v);
        Marshal.WriteByte(bmpwrite.Scan0, j++, (byte)v);
    }

    dstImg.UnlockBits(bmpwrite);
    img.UnlockBits(bmpread);

    return dstImg;
}    

もっと画像をはっきりさせたい

これでもいいんだけど、まだちょっと曖昧だな。認識率もそれほど劇的には上がってない。
もう一工夫必要かしらね。
もう少し曖昧さを排除して……どんくらいかな。
ま、1/3くらいでいいかな(適当)
gyutto.jpg

こんなイメージ。
170以上は一括255に、85以下は一括0にして、その間はそれなりに曖昧にしといて、と。

さあどうなるかな。
まず原本。
org1.jpg
↓ くっきり化
org1.s.jpg
↓ はっきり化
org1.c.jpg

おお。いい感じ。
明るい方もやってみよう。
org6.jpg
↓ くっきり化
org6.s.jpg
↓ はっきり化
org6.c.jpg

はっきり化
internal static Bitmap _correction(Bitmap img)
{
    int width = img.Width;
    int height = img.Height;

    // 出力用のビットマップ
    var dstImg = new Bitmap(width, height);

    // ピクセルに直接アクセスする
    var bmpread = img.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
    var bmpwrite = dstImg.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);

    int blackness = 255;
    int whiteness = -1;

    for (int i = 0; i < (width * height * 3); )
    {
        var r = Marshal.ReadByte(bmpread.Scan0, i++);
        var g = Marshal.ReadByte(bmpread.Scan0, i++);
        var b = Marshal.ReadByte(bmpread.Scan0, i++);

        int ave = (int)new List<int>{ r, g, b }.Average();

        // 最も暗い色を抜き出して「黒」を定義
        blackness = Math.Min(blackness, ave);

        // 128以上の値を平均して「白」を定義
        if (ave >= 128)
        {
            if (whiteness == -1)
                whiteness = ave;
            else
                whiteness = (whiteness + ave) / 2;
        }
    }

    // 暗さの調整
    int darkness = 255 - whiteness;
    whiteness -= darkness;

    // 明るさの調整
    blackness += blackness / 2;

    // 黒と白の数値の差を取り閾値を作る
    int adjust = (whiteness - blackness) / 3;

    blackness += adjust;
    whiteness -= adjust;

    int diff = whiteness - blackness;

    for (int i = 0, j = 0; i < (width * height * 3); )
    {
        var r = Marshal.ReadByte(bmpread.Scan0, i++);
        var g = Marshal.ReadByte(bmpread.Scan0, i++);
        var b = Marshal.ReadByte(bmpread.Scan0, i++);

        // 黒の閾値以下ならそのピクセルは黒
        if (r < blackness ||
            g < blackness ||
            b < blackness)
        {
            Marshal.WriteByte(bmpwrite.Scan0, j++, 0);
            Marshal.WriteByte(bmpwrite.Scan0, j++, 0);
            Marshal.WriteByte(bmpwrite.Scan0, j++, 0);
        }
        // 白の閾値以上ならそのピクセルは白
        else if (r > whiteness ||
                    g > whiteness ||
                    b > whiteness)
        {
            Marshal.WriteByte(bmpwrite.Scan0, j++, 255);
            Marshal.WriteByte(bmpwrite.Scan0, j++, 255);
            Marshal.WriteByte(bmpwrite.Scan0, j++, 255);
        }
        else
        {
            double v = new List<int> { r, g, b }.Average();
            v -= diff;
            v *= 3;

            v = Math.Min(255, Math.Max(0, v));

            Marshal.WriteByte(bmpwrite.Scan0, j++, (byte)v);
            Marshal.WriteByte(bmpwrite.Scan0, j++, (byte)v);
            Marshal.WriteByte(bmpwrite.Scan0, j++, (byte)v);
        }
    }

    dstImg.UnlockBits(bmpwrite);
    img.UnlockBits(bmpread);

    return dstImg;
}

くっきり化とはっきり化は処理が被るところがあるんで、いいようにリファクタリングして下さい。これとあと「すっきり化」ってのも作ってみたんだけど、あんま意味が無かったんでお蔵入り。

さて気になる認識率は・・・?

「くっきり化」→「はっきり化」と補正する事で、数千枚の試験画像での認識率が50%程度から90%以上と劇的に改善、誤読率も改善、やったね。
頭のいい人が研究した論文を活用したとか、そういう賢いものではない、いい加減なやり方なのだけれども、取り敢えずこういうちょっぴり潰れたバーコードでも読めるようになったんで大体オッケーって事です。

しかし、もしかしたら

俺が見つけられなかっただけで、こういう読み取り精度向上のアルゴリズム的なモノってさ、ちゃんとしたのがどっかに転がってたりするのかしら?

もしそういうのをご存知の方は教えて下さい。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2