次の方法で共有


画像ファイル上にある1個のCode39形式のバーコードを読み込んで8桁のコードを得る方法

質問

2022年6月4日土曜日 5:54

画像ファイル上にある1個のCode39形式のバーコードを読み込んで8桁のコードを得る方法

BMPまたはPDFの画像ファイル上にCode39のバーコードが1個だけ印刷されています。
このバーコードは8桁の数字のデータです。
光学的なバーコードリーダー(ハード)で読み込むこともできますが、
光学的なバーコードリーダー(ハード)は使わず、
ソフトウェアでバーコードから8桁のコードを取得したいのですが、
入力がBMPまたはPDFの画像ファイルのフルパス名で、
出力が8桁の数字となるような関数(メソッド)はないでしょうか。

なお、Accusoft Barcode Xpress
https://www.xlsoft.com/jp/products/accusoft/barcodexpress.html
なども試してみましたが、ライセンスの関係で、他のPCでは使えないので、
使用することができませんでしたのでライセンスフリーのものでお願いします。

また、ByteScout Barcode Readerなどのツールがありましたが、
プログラムからツールに、
BMPまたはPDFの画像ファイルのフルパス名を渡したり、
バーコードから得られた8桁の数字データを
プログラムから取り出す仕方が分かりませんでしたので、
関数(メソッド)のようなものはないでしょうか。

VBAのカテゴリーで質問していますが、VBやVC#などでもいいです。

よろしくお願いします。(Windows10)

すべての返信 (14)

2022年6月4日土曜日 8:18

ZXing (ゼブラクロッシング) は如何でしょうか(Apache License 2.0)。
自分は普段、 C# から利用していますが、VBA からも利用できます

VBA の場合は、レジストリ登録後に zxing.interop.tlb を参照設定して、こんな感じ。

Public Sub Test()
    Dim f As String
    f = "C:\test\code39.bmp"
    MsgBox ReadCode39(f), vbInformation Or vbSystemModal
End Sub

Public Function ReadCode39(ByVal imgFile As String) As String
    Dim reader As IBarcodeReader
    Set reader = New BarcodeReader
    reader.Options.PossibleFormats.Add BarcodeFormat_CODE_39
    reader.Options.AssumeCode39CheckDigit = False
    With reader.DecodeImageFile(imgFile)
        If .barcodeFormat And BarcodeFormat_CODE_39 Then
            ReadCode39 = .Text
        Else
            ReadCode39 = ""
        End If
    End With
End Function

2022年6月8日水曜日 9:24

回答ありがとうございます。

教えて頂きました ZXing.Net.0.16.8.0.zip を入手して、
register.cmd でレジストリ登録後、
VBEでzxing.interop.tlb 
(port of the java based barcode scanning library for net (java zxing 29.07.2019 21:30:35))
を参照設定して、教えて頂きましたコードを実行してみました。

【BMPの実行結果】

1個のバーコードが含まれるBMPファイルで確認すると、
If .BarcodeFormat And BarcodeFormat_CODE_39 Then
   ReadCode39 = .text
のBarcodeFormat_CODE_39は4ですが、
.BarcodeFormatが0になり、うまくいきませんでしたが、
Reader.Options.TryHarder = True
を追加すると、.BarcodeFormatが4になり、
.textに8桁のコードが取得されてうまくいきました。

【PDFの実行結果】

しかし、1個のバーコードが含まれるPDFファイルで確認すると、
BarcodeFormat_CODE_39は4ですが、
.BarcodeFormatが2147483647になっていて、
.textは空白になってしまい、うまくいきませんでした。
別のバーコードが含まれるPDFファイルをいくつか試してみましたが、
すべてうまくいきませんでした。
いずれも、.BarcodeFormatが同じ値の2147483647になっていて、
.textは空白になっていました。

Reader.Options.TryHarder = True
Reader.Options.PureBarcode = True
などを追加してみましたがうまくいきませんでした。

また、
Reader.AutoRotate = True
Reader.TryInverted = True
を追加するとエラーになりますが、
これらはVBAではどのように記述すればよいのでしょうか。

PDFファイルのバーコードの解析の精度を挙げるような
オプションがほかにもありましたら教えてください。

よろしくお願いします。


2022年6月8日水曜日 11:34

2147483647 は &H7FFFFFFF& ですから、ビットフラグ &H4& を内包しますね。

DecodeImageFile メソッドは、その名の通り「画像ファイル」を読み取るためのものです。
pdf ファイルではなく、bmp や png などのファイルを渡してください。

今回は PDF と BMP の両方を読み取りたいわけではなく、「BMPまたはPDF」のどちらかが読めれば良いのですよね?

Zxing.net のサイトには、 PDF 内の画像を取り出す C# でのサンプルもあります。
このサンプルでは、 PdfSupport クラスの GetBitmapsFromPdf メソッドで pdf 内の画像を列挙する実装になっています、そのために PdfSharp というライブラリを利用しています。

なお、PdfSharp を VBA で使おうとする場合は、一手間必要なようです。


2022年6月9日木曜日 10:23

回答ありがとうございます。

ZXing.Net.DemoClients.0.16.8.0.zipのWindowsFormsDemo.exeで、
PDFファイルのバーコードから8桁のコードが取得できたので、
教えて頂きましたコードの手直しで解決できると思っていたのですが、
PDFをBMPに変換して処理していたのですね。

WindowsFormsDemo.exeのコードが見当たりませんでしたが、
教えて頂きました「C# でのサンプル」のリンク先にあったのですね。

でも、なんか、ExportAsPngImageの関数の中のコメントによると、
(You can put the code here that converts vom PDF internal image format to a Windows bitmap)
PDF内部画像形式をWindowsビットマップに変換するコードを、
ユーザー自身が記述しないといけないようなことが書かれています?

WindowsFormsDemo.exeのコードから、
入力としてバーコードのPDFファイルのフルパスを渡して、
出力として8桁のコードが生成されるような関数の形にした後、
DLLの形で出力して、VBAでこのDLLの中の関数を呼び出すような
ことを思いついたのですが、
「C# でのサンプル」のリンク先のコードを集めて、
自力でビルドできるように構築する技量はありませんが、
ビルドが通ってデバッグ実行できる環境からであれば、
試行錯誤して時間をかければ何とかできそうな気がします。

Zxing.net のサイトに、
WindowsFormsDemo.exeのコード一式が、
VisualStudioのプロジェクトのような形で、
用意されてはいないでしょうか。

よろしくお願いします。


2022年6月9日木曜日 10:53

小生もZXingは検索していたのですが、使い方が少し面倒かなと感じ(理解不足もある)、他の方法を探していてIronSoftware社のIronBarcode.dllというモジュールを、VC#で試してみました。この方法では一つの関数で画像イメージもPDFも処理できました。

ここのフォーラムでThird Partyのモジュールについて、どこまで書いていいのかわかりませんが、小生の試した手順は以下の通りです。

【DLLの入手とインストール】
https://ironsoftware.com/csharp/barcode/
一番下までスクロールし、
「Try IronBarcode Free」の「Download DLL」をクリックして「IronBarCode.zip」をダウンロードする。
この中の「bin - net40」フォルダーにある「IronBarcode.dll」を「C:\Windows\System32」にコピーする。

【参照設定】
VSで「C# Windows Form .NET Framework」プロジェクトを作成する。
プロジェクト-参照の追加-[参照...]
「C:\Windows\System32」に移動して、「IronBarcode.dll」を選択、[追加]をクリックする。

【CSコード】
下記のサンプルコードはWindows Formアプリ(.NET Framework)で試したものです。
任意のイメージファイルを選択できるようにして、そのファイルのバーコード(LinearもQRもPDFも可)のデータからテキストを返すものです。

イメージ上の余白やフォーカス(out-of-focusかin-focusか)の程度によってはエラーになるようですが、鮮明な画像であれば精度がいいと感じました。

using IronBarCode;

        private string ReadBarCode(string imagefile)
        {
            BarcodeResult Result = BarcodeReader.QuicklyReadOneBarcode(imagefile, BarcodeEncoding.All);
            return Result.Text;
        }

        private void btnSelectFile_Click(object sender, EventArgs e)
        {
            try
            {
                tbFullPath.Text = "";
                OpenFileDialog ofd = new OpenFileDialog();
                if (DialogResult.OK == ofd.ShowDialog())//ファイルを選択
                {
                    tbFullPath.Text = ofd.FileName.Trim();
                    tbBCData.Text = ReadBarCode(tbFullPath.Text);//ファイルを渡すとテキストで返ってくる。読めないときはnullが返る。
                }
                else
                {
                    ;
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "BCRTest", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

運用にあたっては、取得した文字列から必要な桁数分を切り出せばいいと思います。


2022年6月9日木曜日 15:19

VBA のカテゴリーから離れてきちゃいましたね…まぁいいか。

WindowsFormsDemo.exeのコード一式

先の URL の通り GitHub で公開されているものですから、
Visual Studio をお持ちであれば、[ファイル]-[リポジトリのクローン]で
https://github.com/micjahn/ZXing.Net.git
直接指定してクローンすれば、Visual Studio でソリューションを開けますよ。
その中に、WindowsFormsDemo のプロジェクトも含まれています。

Visual Studio を使わず、ブラウザーから直接ダウンロードする場合は、
https://github.com/micjahn/ZXing.Net/archive/refs/heads/master.zip
でも構いません。

※リポジトリ全体なので、WindowsFormsDemo 以外のソースも含まれます。

PDF内部画像形式をWindowsビットマップに変換するコードを、
ユーザー自身が記述しないといけないようなことが書かれています?

現状、WindowsFormsDemo.exe で取得できていたのですよね?

その場合、PDF 内の image XObject が DCTDecode フィルターであったはずで、その場合は ExportJpegImage の方が呼ばれます。JPEG 2000 画像 (JPXDecode フィルター) の処理は組み込まれていないですが。

可逆圧縮である FlateDecode フィルターだった場合は、ExportAsPngImage の方が呼ばれる実装になっていますが、御認識の通り、こちらは現在 return null; しているだけで画像を返す形にはなっていません。ExportAsPngImage を実装しておきたい場合には こちらを試してみてください。

他にも、FAX 受信の PDF だと CCITTFaxDecode フィルターだったり、4KB 以下の小さい画像だとインライン画像オブジェクトが使われるものがあったりと様々な形式があるわけですが……ひとまず今回の要件であれば、実際に使われている最小限の物だけ準備しておけば良いかと思います。

ちなみに、PDF 操作用のライブラリとしては、 今回の PDFSharp の他だと、iTextSharp / iText.NET などがありますが、こちらはライセンス的に少々扱いにくく、自分は使ったことがありません。

ライセンスフリーのものでお願いします。

ZXing は Apache License 2.0 、PDFSharp は MIT License です。
無償で利用できますが、ライセンスフリーというわけではありません。

そのほか別案としては、WinRT の Windows.Data.Pdf を用いるという選択肢もあるかも…? (Windows 7 以下には非対応)


2022年6月10日金曜日 0:24

【IronBarCodeの件訂正】

VS内から実行すると期待の動作をするのですが、VS外で起動した場合、エラーになりました。「Try IronBarcode Free」という文言につられて「Free」と思ったのですが、開発環境外で実行する場合、Licenseが必要なのかもしれません。「Tryすること」がFreeなのかも???。調査不足で混乱させて申し訳ありません。


2022年6月10日金曜日 4:56

そのほか別案としては、WinRT の Windows.Data.Pdf を用いるという選択肢もあるかも…?

ZXing.NET ; Windows.Data.Pdf の組み合わせで作ってみました。

GitHub の Benshi/Code39ReaderSampleソース一式を置いときます。

C# 等から呼ぶことを前提にしていますが、一応 COM 対応してあるので
登録すれば VBA や VBS からも呼べることは確認済み。

codeArray = CreateObject("Orator.Code39Reader").ReadFromFile("D:\example.pdf")

2022年6月10日金曜日 6:13

【IronBarCodeの件訂正】

「Tryすること」がFreeなのかも???

30日間の Trial が無料です。nuget はこちら

機能要件を満たせるかどうか、購入前に使ってみて判断できるということであって、実運用においては費用が発生するはずです。

  • 最低額の Lite プランが $499 (再頒布不可)
  • 10ライセンスの Professinal が $999 (再頒布は ;$1,599)
  • 無制限の Unlimited プランが $2,999 (再頒布は ;$3,999)

2022年7月10日日曜日 10:49

ZXing.NET ; Windows.Data.Pdf の組み合わせで、
Code39ReaderSampleを作っていただき、COM対応もしていただいて、
VBScriptからも呼び出せることを確認していただきまして、
とても感謝しております。ありがとうございます。

その後の経緯ですが、
Visual Studio 2022(community)で、
Code39ReaderSampleのソリューションファイルを読み込んで、
ビルドは正常に行われ、COMオブジェクトのDLLが作成されて、
レジストリにも登録されていることは確認済みですが、
VBScriptから呼び出しても戻り値がうまく取得できません。

具体的には、下記のVBScriptのコードの(A)の部分で、
エラー : 型が一致しません。
コード : 800A000D
ソース : Microsoft VBScript実行時エラー
というエラーになってしまいます。

VB.NET側の戻り値がString()型で、
VBScript側の受け取る配列変数(ここではcodeArray)も、
呼び出し後はString()型になっているようなのですが、
なぜか codeArray(0)から値が取り出せなくて、
試行錯誤しながらいろいろやっているのですが、
この原因がよく分かりません。

Code39ReaderSampleのコードを作っていただいた上に、
大変お願いし辛いのですが、
今現在も解決できなくてお手上げの状況ですので、
VBScript側のコードも教えて頂くことはできないでしょうか。

よろしくお願いします。

test.vbs

Dim codeArray

'Dim obj
'Set obj = CreateObject("Orator.Code39Reader")
'codeArray = obj.ReadFromFile("C:\test\test.pdf")

WScript.Echo TypeName(codeArray)     'Empty

codeArray = CreateObject("Orator.Code39Reader").ReadFromFile("C:\test\test.pdf")

WScript.Echo TypeName(codeArray)     'String()
WScript.Echo UBound(codeArray)       '0
WScript.Echo LBound(codeArray)       '0

WScript.Echo codeArray(0)            '<==(A)

'For index = 0 To UBound(codeArray)
'  WScript.Echo codeArray(index)     '<==(A)
'Next


2022年7月10日日曜日 12:07

あー…済みません。元が VBA カテゴリーだったので、VBA (と .NET) からの呼び出ししかテストしていませんでした。
VBScript から呼べることまでは確認しましたが、その値を利用できなくては意味が無いですよね。

戻り値のデータ型を、As String() から As Object または As Object() に変更することで、VBScript からでも呼び出せるようになります。GitHub のソースは直しておきました

※VBScript には「String 型の配列」という概念が無いため、ライブラリが扱う配列は Variant 型の配列とせねばなりません。


2022年7月28日木曜日 9:35

修正をしていただきましてありがとうございます。

Code39ReaderSample-0.2で確認しましたところ、
型が一致しませんというエラーはでなくなりましたが、
バーコードの値が正しく検出されませんでした。

PDFファイルはA4サイズ(横長)で、
ピクセル値で左上を(0,0)とすると右下が(1116,783)で、
バーコードの位置は(867,658)-(1044,716)にあります。
このPDFファイルを読み込ませても正しく検出されませんが、
PDFファイルのバーコードの部分だけを切り出して、
元のPDFファイルのサイズにまで拡大したPDFファイルを読み込ませると、
正しく検出されました。

このため、Windows.Data.Pdfで、
(867,658)-(1044,716)の範囲をトリミングして、
元のPDFファイルのサイズである(0,0)-(1116,783)に拡大したいのですが、
いろいろ調べて試してみましたがトリミングの設定の仕方と、
トリミングした部分の拡大の仕方がよく分かりませんでした。

これまでに調べたことを以下に記しておきます。

修正して頂きましたCode39ReaderSample-0.2のコードに、
下記の(A)のコードを追加して変換後のBMPファイルを出力して確認すると、
バーコードの線と線の間にぼやけた線が表示されていて、
これでは明らかにまずいということが確認できました。
それで、Windows.Data.Pdfのサイト
https://docs.microsoft.com/ja-jp/uwp/api/windows.data.pdf?view=winrt-20348
のドキュメントをいろいろ調べてみましたところ、
PdfPageDimensionsクラスの中にTrimBoxというプロパティがあり、
トリミングすることができそうなことが分かりましたが、
このクラスではトリミングの範囲の設定を読み取るだけで設定することができません。
PdfPageRenderOptionsクラスのSourceRectプロパティで、
トリミングの範囲を設定することができて、
RenderToStreamAsync(IRandomAccessStream)の代わりに、
RenderToStreamAsync(IRandomAccessStream, PdfPageRenderOptions)を使うことにより、
指定したトリミングの範囲を切り出すことができるように理解しましたので、
下記の(B)(C)(D)のようなコードを記述しましたが、(C)の
options2.SourceRect.X
options2.SourceRect.Y
options2.SourceRect.Width
options2.SourceRect.Height
の部分に赤色波線が表示されて、
BC30068:式が値であるため、代入式のターゲットにすることはできません。
というエラーになります。
トリミングの範囲を指定するにはどのように記述すればよいのでしょうか。
また、トリミングできないので確認することができませんでしたが、
下記の(E)のような記述で、トリミングした(867,658)-(1044,716)の部分が、
元のPDFファイルのサイズである(0,0)-(1116,783)に拡大される
という理解で正しいでしょうか。

よろしくお願いします。

Code39Reader.vb

    (省略)
Public Function ReadFromFile(filePath As String) As String()
    (省略)
  For Each bmp In GetBitmaps(filePath)
    '確認用BMPファイルの出力                                          '【追加】
    bmp.Save("C:\test\test.bmp")                                     '【追加】(A)
    (省略)
End Function
    (省略)
Private Function GetBitmaps(filePath As String) As Bitmap()
    (省略)
  Using page = doc.GetPage(pageIndex)
  'PDFファイルの画像サイズの確認                                      '【追加】
  Dim pdf_width As Double                                             '【追加】
  Dim pdf_height As Double                                            '【追加】
  pdf_width = page.Size.Width                                         '【追加】
  pdf_height = page.Size.Height                                       '【追加】
  MsgBox("page.Size.Width = " & pdf_width)        ' 1116.47998046875  '【追加】
  MsgBox("page.Size.Height = " & pdf_height)      ' 783.360046386719  '【追加】

  Dim options2 = New PdfPageRenderOptions                             '【追加】(B)

  'バーコード部分をトリミングする(うまくいかない)                     '【追加】
  MsgBox("SourceRect.X(変更前) = " & options2.SourceRect.X)           '【追加】
  MsgBox("SourceRect.Y(変更前) = " & options2.SourceRect.Y)           '【追加】
  MsgBox("SourceRect.Width(変更前) = " & options2.SourceRect.Width)   '【追加】
  MsgBox("SourceRect.Height(変更前) = " & options2.SourceRect.Height) '【追加】
  options2.SourceRect.X = 867                                         '【追加】(C)
  options2.SourceRect.Y = 783                                         '【追加】(C)
  options2.SourceRect.Width = 177                                     '【追加】(C)
  options2.SourceRect.Height = 58                                     '【追加】(C)
  MsgBox("SourceRect.X(変更後) = " & options2.SourceRect.X)           '【追加】
  MsgBox("SourceRect.Y(変更後) = " & options2.SourceRect.Y)           '【追加】
  MsgBox("SourceRect.Width(変更後) = " & options2.SourceRect.Width)   '【追加】
  MsgBox("SourceRect.Height(変更後) = " & options2.SourceRect.Height) '【追加】

  'トリミングした部分を元のPDFサイズに拡大する                        '【追加】
  MsgBox("DestinationWidth(変更前) = " & options2.DestinationWidth)   '【追加】
  MsgBox("DestinationHeight(変更前) = " & options2.DestinationHeight) '【追加】
  MsgBox("PreferredZoom(変更前) = " & page.PreferredZoom)             '【追加】
  options2.DestinationWidth = CUInt(pdf_width)                        '【追加】(E)
  options2.DestinationHeight = CUInt(pdf_height)                      '【追加】(E)
  MsgBox("DestinationWidth(変更後) = " & options2.DestinationWidth)   '【追加】
  MsgBox("DestinationHeight(変更後) = " & options2.DestinationHeight) '【追加】
  MsgBox("PreferredZoom(変更後) = " & page.PreferredZoom)             '【追加】

  Try
    Dim memStream As New MemoryStream()
    'page.RenderToStreamAsync(memStream.AsRandomAccessStream()).GetResults()
    'page.RenderToStreamAsync(memStream.AsRandomAccessStream()).AsTask().GetAwaiter().GetResult() '【コメントアウト】
    page.RenderToStreamAsync(memStream.AsRandomAccessStream(), options2).AsTask().GetAwaiter().GetResult()  '【追加】(D)
    (省略)

2022年7月28日木曜日 11:57

RenderToStreamAsync(IRandomAccessStream, PdfPageRenderOptions)を使うことにより、指定したトリミングの範囲を切り出すことができるように理解しましたので、

そうですね。
PdfPageRenderOptions の SourceRect に、PDF 内の矩形領域を指定してレンダリングします。出力後のサイズは DestinationWidth と DestinationHeight です。

PdfPageDimensionsクラスの中にTrimBoxというプロパティがあり、
トリミングすることができそうなことが分かりましたが、

Dimensions プロパティは、PDF 各ページの「境界ボックス」を示すものですね。ツールによっては、TrimBox=仕上がりサイズ、CropBox=トリミングサイズ、BleedBox=立ち落しサイズ…などと表記されることもあります。.X や .Y が 0 であるとは限らない点に注意。

これらの領域は、Adobe Reader で言えば、[表示]-[ズーム]の「幅に合わせる」と「描画領域の幅に合わせる」などで使われています。

実際の PDF ファイルをメモ帳で開いてみると、/MediaBox や /CropBox や /TrimBox といった座標情報が記録されていることを確認できるはずです。

PDF 内にはポイント単位系(1/72インチ)で記録されていますが、Dimensions プロパティや Size プロパティでは、Windows における DIUs 単位(1/96 インチ単位)にした値となります。実際のバーコードの位置と違っていないかを確認してみてください。


2022年8月2日火曜日 10:27

SourceRectで切り出してDestinationWidth,DestinationHeightで拡大できることや、
PDFではポイント単位系(1/72インチ)、Windows.Data.PdfではDIUs単位(1/96 インチ単位)
の違いがあることを教えていただきましてありがとうございます。

options2.SourceRect.X = 867
options2.SourceRect.Y = 783
options2.SourceRect.Width = 177
options2.SourceRect.Height = 58
の部分のBC30068:式が値であるため、代入式のターゲットにすることはできません。
というエラーですが、
Public Property SourceRect As RectのRectのNamespaceがWindows.Foundationになっているので、
options2.SourceRect = New Windows.Foundation.Rect(875, 670, 160, 40)
というように書き換えてみましたところうまくいきました。
(こういったところが分からなくて時間がかかっています)

肝心の結果ですが、
PDFファイルを読み込ませると、
バーコードの部分だけがトリミングされて、拡大もされていましたが、
バーコードの値は正しく検出されませんでした。
BMP変換後の出力したBMPファイルを見てみると、
なぜこんなにぼけているのだろうかと思えるほどよくない画像になっています。
トリミングなし(取得状態)、トリミングあり(原寸)の画像と比べると、
トリミングして拡大した画像は、他の画像に比べて多少は良くなってはいますが、
いずれもバーコードとしてはとても読み取れるとは思えないほどよくない画質です。

BMP変換後のBMPファイルの画像の解像度は96DPIになっていますが、
試しに、PDFファイルを印刷した用紙をスキャナーでスキャンすると、
300DPIでスキャンした場合のバーコードははっきりしていますが、
(96DPIの選択がないので最低の)100DPIでスキャンした
バーコードはぼやけた状態になっています。
このようなことより、96DPIの低解像度に原因があるような気もしていますが、
PDFファイルから読み込まれた時点での画像が既に低解像度になっているのかもしれませんので、
BMP変換前のPDFの画像がどのようになっているのかを見てみたいのですが、
取得後のPdfPageオブジェクトのPDF画像を、
PDFファイルとして出力することはできますでしょうか。
PDFファイルとして出力することが難しければ、
PDFの画像を表示させることはできますでしょうか。

よろしくお願いします。