ZXingでバーコードアプリ開発(3)

GS1-128, GS1 DataMatrixの生成時にFNC1を埋め込む件

GS1を扱うときにFNC1の処理は避けて通れないのですが、ZXingがFNC1をどう扱っているのかが不明でした。日本語のページでもそのことに言及しているサイトが見つからなかったので、ZXingでエンコードしたバーコードをデコードしてRawデータを確認することにしました。

FNC1をバーコードに埋め込むルールは以下の通りです。

(1)可変長のセクションの後に付ける

  AI(10)のロットや、AI(30)の入り数などにおいて、セクションの終わりの印として使います。これは、GS1-128 と GS1 DataMatrix の両方に共通するルールです。

(2)GS1-128のスタートコードの後に付ける

  GS1-128とCODE128を区別するため、スタートコードの後にFNC1を埋め込むことになっています。

(3)GS1 DataMatrixの先頭

  GS1 DataMatrixでは必ずFNC1を先頭に付けます。

尚、読み取り(デコード)時はFNC1は「GS (グループセパレータ)」に置き換えて出力することになっています。 GSはASCIIコード29の制御文字なので、印刷しても出力されません。そのため、市販のバーコードスキャナではGSをスペースに置き換えるなどの機能がついていたりします。

で、ZXingでバーコードを生成するときに、FNC1やSTARTコードをどのように指定すればよいのかを調べたところ、以下の通りとなりました。

表.ZXing.BarcodeWriterのGS1エンコードの動作

フォーマットコード解釈C#でZxingに
渡す文字
バーコード内のコード
GS1-128105START C不要 (注1)105
GS1-128102FNC1(char)0x00F193 (注2)
GS1 DataMatrix232先頭のFNC1(char)29232 (注3)
GS1 DataMatrix232可変長項目の
終端につけるFNC1
(char)2930(注4)

【注1】 ZXingではBarcodeFormatの値を「CODE_128」に指定すると自動的にスタートコードとして「105」が埋め込まれます。 ちなみに、文字列の途中でアルファベットを検知すると「CODE B」への切り替え文字も自動的にバーコードに埋め込んでくれます。

【注2】 GS1-128での「93」は制御文字「GS (ASCII 29)」を表します

【注3】 GS1 DataMatrixでは先頭は必ずFNC1と決められています。ZXingはBarcodeFormatの値を「DATA_MATRIX」にすると、GS (ASCII 29)を「232(FNC1)」に変換してバーコードに埋め込んでくれます。

【注4】 ZXingはBarcodeFormatを「DATA_MATRIX」にすると、先頭以外のGS (ASCII 29)は 「30 (GS)」をバーコードに埋め込みます。

例えば、C#で書くとこんな感じになります、、

// (01)14512345678903(10)ABCD123(17)201201 をエンコードする例
//writer.Optionsによる画像サイズや余白マージンの指定などはここでは省きます

BitMatrix bitMatrix;
ZXing.BarcodeWriterPixelData writer = new BarcodeWriterPixelData();
string _code;

//GS1-128の場合
writer.Format = BarcodeFormat.CODE_128;
_code = $"{(char)0x00F1}"
      + "0114512345678903"
      + "10ABCD123" + $"{(char)0x00F1}"
      + "17201201";
bitMatrix = writer.Encode(_code);  

//-----------------------------------
//bitMatrixからBitmapを作成する処理 (省略)
//-----------------------------------


//GS1 DataMatrixの場合
writer.Format = BarcodeFormat.DATA_MATRIX;
_code = $"{ (char)29}" 
      + "0114512345678903"
      + "10ABCD123" + $"{ (char)29}" 
      + "17201201"
bitMatrix = writer.Encode(_code);

//-----------------------------------
//bitMatrixからBitmapを作成する処理 (省略)
//-----------------------------------

調査してみて、、、

DataMatrixの可変長項目のFNC1に「GS(ASCII 29) を指定すると、コードワード232を指定したことになる」っていうところが分かりにくかったですね。コードワードにも「30」というGSを表すコードがあるのがややこしい・・・。調査に何日かかかってしまいました。

ZXingでバーコードアプリ開発(2)

前回の投稿の続きです。

2値画像化してからのバーコード認識にトライしていましたが、ようやくできました! とりあえずは。

Xamarin.AndroidのコードなのでAndroid限定ですが、Android.Hardware.Camera2 のキャプチャ画面からByte配列の画像データを取得するところは、

https://github.com/vtserej/Camera2Forms

を参考にしました。

このサンプルの中にある CameraViewServiceRenderer.OnPhoto() にて、画像のバイト配列が取得できるようになっています。

private void OnPhoto(object sender, byte[] imgSource)
{
    //imgSourceにカメラビュー画像が入っているので、
    //ここで煮るなり焼くなり


    Device.BeginInvokeOnMainThread(() =>
    {
        _currentElement?.PictureTaken();
    });
}

AndroidのCamera2からシャッター音を鳴らさずに無音で画像を取得するには、このサンプルがちょうどいいかと思います。

画像はARGB画像です。ARGBの4種類がそれぞれ1バイト(256諧調)なので、1ピクセル4バイトになります。

2値化画像にする手順は、

  • グレースケール化(RGBの3色の数値を平均化する)
  • 閾値を決めて、1ピクセルごとに「0 or 255」にする

ということになります。

これも変換関数を作って何とかなりました。・・・が、肝心のバーコード認識率はほとんど変わりませんでした。

それぞれ、生画像、グレースケール、2値化画像を比較したのですが、2値化の段階で1次元バーコードの斜め線になったところが「ギザギザ」になっていました。

また、照明の当たり具合はバーコードの部分ごとに異なることが当然あります。すると、ハレーションのような現象が出て、バーコードが部分的につぶれてしまいます。

下の画像は桁数がまあまあ多いGS1-128のバーコードで、真ん中あたりが照明で明るく照らされていたものです。

2値化したことにより、バーコードそのものが部分的にかすれてしまっています。

つまり、単純な2値化の閾値設定では状況の変化に対応できず、意味がないことが分かりました。今まで画像処理に縁がなかったのですが、なかなか手ごわいです。

画像処理のサイトを検索すると、いろいろな画像処理のテクニックがあるようですが、今のところ有効そうなのは、

  • カメラビューまたはグレースケール化のタイミングで解像度を上げる
  • 閾値方式だけではなく、周囲のピクセルとの比較によって白 or 黒を決定し、なめらかな線を目指す
  • 1ピクセルあたりのバイト数を減らす (高速化のため)

といったところでしょうか。(自分にできるのか不安)

GooglePlayを漁っていると、GS1-128でアルファベットや桁数を多く含み、しかも撮影条件が悪くても高速に読み取るソフトが中には存在します。キーエンスのハンディターミナルと同等レベル? ってぐらい優秀です。

一体、どうやって実現しているんだろうか?

[追記 2020-09-11]

GS1-128の読み取りが悪いですが、逆に、GS1-128以外はそれなりの読み取り速度なので、画像処理は時間がかかりそうなのであきらめました。今後の努力課題として、取り急ぎGS1 DataMatrixの生成処理をすることにしました。