ZXing-C++をVS2022でつかう
では、実際にVS2022でZXing-C++を使っていきます。
前回、 CMAKE で ZXing-C++ を VS2022でビルドできるようにしました。
そうそう、
cmake --install .
このインストールですが、CMAKEの--install って名前がややこしいだけで、別にWindowsにアプリをインストールしてるわけじゃありません。
build で完成した .lib と .h を指定フォルダに整理してコピーしてるだけですのでガンガンやってしまって問題ありません。
それと前回は、
D:\libs\zxing-install
にインストールしましたが、フォルダ名が長くなってしまうので、今回は
D:\libs\zxing
に、インストールしなおしております。
なので今回のコードは/zxing フォルダーに資源がインストールされている状態で実行しています。
では、前回つくったQRDecodeTestをビルドしてみます。
エラー LNK2038 'RuntimeLibrary' の不一致が検出されました。値 'MD_DynamicRelease' が MDd_DynamicDebug の値 'QRDecodeTest.obj' と一致しません。 QRDecodeTest
・・・は?
コンパイルは通った、リンクで死んだ
勘のいい方ならもうお気づきでしょう。
ZXing → Release版しか作ってない
QRDecodeTestプロジェクト → Debugでビルド
つまり、Debugビルドなのに Release用で作成されていた zxing.lib をリンクしようとした ため、リンカがエラーとなってしまっていたのです。
VSではDebugビルドのEXEは、Debug用設定で作られたライブラリを前提にリンクしようとする仕様です。
ですので、Debugビルド用に ZXing-C++ もDebug版の zxing.lib を作っておく必要があります。
CMakeでのDebug用のビルドは簡単です。
cmake --build . --config Debug
この様に、--Config Debugを指定してCMakeするだけです。
ところが今度は、別の問題が発生しました。
ZXing-C++ のCMakeはReleaseであってもDebugであっても、出来上がるライブラリファイル
名は zxing.lib で固定されていたため、前回作ったRelease用の zxing.lib をDebug用のLibで上書きしてしまっていたのです。
CMAKEの構成によっては、Debug版は zxingd.lib という感じで末尾へ'd'を追加して名前を変えてくれてたりするものもあったりするのですが、どうやら ZXing-C++は違うようです。
こんな時に一番わかりやすいのは、
cmake --install . --config Release --prefix D:\libs\zxing\Release
cmake --install . --config Debug --prefix D:\libs\zxing\Debug
この様にインストールフォルダを分けてしまうことです。
D:\libs\zxing\
Debug\
include\
lib\
ZXing.lib
Release\
include\
lib\
ZXing.lib
VS側は構成ごとに、
D:\libs\zxing\Debug\lib
D:\libs\zxing\Release\lib
を指定すれば大丈夫です。
・・・いやまて、これだと Debug と Release の /include に同じヘッダファイルができてるじゃん。
細かい事なのですが、どーしても気になってしまい、今回はDebugビルドした ZXing.lib を ZXingd.lib にリネームし、Releaseビルドした /lib へ手動で放り込み、最終的には以下のような配置にしました。
D:\libs\zxing\
include\
lib\
ZXing.lib
ZXingd.lib <- Debug用lib
これでなんとかビルドが通る様になりました。
初版のテストコードを実行してみて気づいた点を直したのが以下の第2版です。
第2版の修正点
- min/maxマクロとstd::min/maxの関数名が衝突するため、#define NOMINMAXディレクティブを追加
- コンソールの文字コードがUTF-8だと文字化けしてしまったため、SetConsoleOutputCP(CP_UTF8);
SetConsoleCP(CP_UTF8);
で対処
#define NOMINMAX
#include <windows.h>
#include <gdiplus.h>
#include <iostream>
#include <string>
#include <vector>
#include <ZXing/ReadBarcode.h>
#include <ZXing/ImageView.h>
#include <ZXing/ReaderOptions.h>
#include <ZXing/BarcodeFormat.h>
#pragma comment(lib, "gdiplus.lib")
#ifdef _DEBUG
#pragma comment(lib, "ZXingd.lib")
#else
#pragma comment(lib, "ZXing.lib")
#endif
std::wstring ToWide(const std::string& s)
{
int len = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, nullptr, 0);
std::wstring ws(len - 1, L'\0');
MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, ws.data(), len);
return ws;
}
bool LoadImageAsGray(const std::wstring& path, std::vector<uint8_t>& gray, int& width, int& height)
{
Gdiplus::Bitmap bmp(path.c_str());
if (bmp.GetLastStatus() != Gdiplus::Ok)
return false;
width = bmp.GetWidth();
height = bmp.GetHeight();
gray.resize(width * height);
for (int y = 0; y < height; ++y)
{
for (int x = 0; x < width; ++x)
{
Gdiplus::Color c;
bmp.GetPixel(x, y, &c);
uint8_t g = static_cast<uint8_t>(
(c.GetRed() * 299 + c.GetGreen() * 587 + c.GetBlue() * 114) / 1000
);
gray[y * width + x] = g;
}
}
return true;
}
int main(int argc, char* argv[])
{
SetConsoleOutputCP(CP_UTF8);
SetConsoleCP(CP_UTF8);
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken = 0;
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr);
std::wstring path = ToWide(argv[1]);
std::vector<uint8_t> gray;
int width = 0;
int height = 0;
if (!LoadImageAsGray(path, gray, width, height))
{
std::cout << "Failed to load image.\n";
Gdiplus::GdiplusShutdown(gdiplusToken);
return 1;
}
ZXing::ImageView image(
gray.data(),
width,
height,
ZXing::ImageFormat::Lum
);
ZXing::ReaderOptions options;
options.setFormats(ZXing::BarcodeFormat::QRCode);
options.setTryHarder(true);
auto result = ZXing::ReadBarcode(image, options);
if (result.isValid())
{
std::cout << "QR detection successful.\n";
std::cout << result.text() << "\n";
}
else
{
std::cout << "Code not found.\n";
}
Gdiplus::GdiplusShutdown(gdiplusToken);
return 0;
}
ということで、第2版でテストしてみます。
QrDecodeTest.exe TestQr.png
QR detection successful.
Win32は死なズ。
ちゃんとQRコードを認識してデコードできていることが確認できました。
ZXing-C++ すごい。
ZXing-C++をさらに使う
ここまででも ZXing-C++ は十分すばらしいライブラリということが分かりますが、実は
ZXing-C++ の真の力はこんなもんじゃありません。
ZXing-C++には、
ZXing::ReaderOptions options;
があり、画像の捜査時に様々なオプションを付与することが可能です。
まず、
options.setFormats(ZXing::BarcodeFormat::All);
これは ZXing-C++ が対応できる形式すべてを対象にする。 という意味になります。
前回までは、ZXing::BarcodeFormat::QRCodeとして、画像に含まれるQRコードのみを検出していましたが、Allにする事で認識できる全てのバーコードを検出できる様になります。
さらに、
options.setTryHarder(true);
これは、通常探索で見つからなくても、もう少し粘って探す。 という設定です。
例えば、
小さいバーコード
低解像度
複数コードがある
といった場合に効果が期待できます。
ただし、探索を頑張る分だけ処理時間は少し増える可能性があります。
さらに、
options.setTryRotate(true);
これは、 回転したコードも探す設定。 です。つまり、横向きになっている画像であってもそのに含まれるバーコードを識別できる様になります。
まだまだあります、
options.setTryDownscale(false);
これは、 大きい画像を縮小して試すかどうかの設定 なんだけど、小さくしたせいでバーがつぶれて読めなくなってしまう場合があるので今回は OFF にしています。
最後に、
options.setMaxNumberOfSymbols(100);
これは、 最大100個まで検出していい です。
一つの画像にバーコードが100個書いてある状況もなかなか無いとは思いますが。
これらオプションを全部乗せして、さらに大きな画像を読み込める様にするため、これまではGdiplusの、 Gdiplus::GetPixel() を使っていましたがこの関数はやや低速なため、より高速な Gdiplus::LockBits() を使って画像を読み込むようにしました。
出来上がった第3版のコードは以下の通りです。
#define NOMINMAX
#include <windows.h>
#include <gdiplus.h>
#include <iostream>
#include <string>
#include <vector>
// ZXing
#include <ZXing/ReadBarcode.h>
#include <ZXing/ImageView.h>
#include <ZXing/ReaderOptions.h>
#include <ZXing/BarcodeFormat.h>
#pragma comment(lib, "gdiplus.lib")
#ifdef _DEBUG
#pragma comment(lib, "ZXingd.lib")
#else
#pragma comment(lib, "ZXing.lib")
#endif
std::wstring ToWide(const std::string& s)
{
int len = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, nullptr, 0);
std::wstring ws(len - 1, L'\0');
MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, ws.data(), len);
return ws;
}
bool LoadImageAsGray(const std::wstring& path, std::vector<uint8_t>& gray, int& width, int& height)
{
Gdiplus::Bitmap bmp(path.c_str());
if (bmp.GetLastStatus() != Gdiplus::Ok)
return false;
width = static_cast<int>(bmp.GetWidth());
height = static_cast<int>(bmp.GetHeight());
Gdiplus::Rect rect(0, 0, width, height);
Gdiplus::BitmapData data{};
Gdiplus::Status st = bmp.LockBits(
&rect,
Gdiplus::ImageLockModeRead,
PixelFormat32bppARGB,
&data
);
if (st != Gdiplus::Ok)
return false;
gray.resize(width * height);
const BYTE* base = static_cast<const BYTE*>(data.Scan0);
for (int y = 0; y < height; ++y)
{
const BYTE* row = base + y * data.Stride;
if (row) {
for (int x = 0; x < width; ++x)
{
const BYTE b = row[x * 4 + 0];
const BYTE g = row[x * 4 + 1];
const BYTE r = row[x * 4 + 2];
gray[y * width + x] = static_cast<uint8_t>(
(r * 299 + g * 587 + b * 114) / 1000
);
}
}
}
bmp.UnlockBits(&data);
return true;
}
int main(int argc, char* argv[])
{
SetConsoleOutputCP(CP_UTF8);
SetConsoleCP(CP_UTF8);
if (argc < 2)
{
std::cout << "Userge: QrDecodeTest.exe [image.png]\n";
return 0;
}
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken = 0;
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr);
std::wstring path = ToWide(argv[1]);
std::vector<uint8_t> gray;
int width = 0;
int height = 0;
if (!LoadImageAsGray(path, gray, width, height))
{
std::cout << "Failed to load image.\n";
Gdiplus::GdiplusShutdown(gdiplusToken);
return 1;
}
ZXing::ImageView image(
gray.data(),
width,
height,
ZXing::ImageFormat::Lum
);
ZXing::ReaderOptions options;
options.setFormats(ZXing::BarcodeFormat::All);
options.setTryHarder(true);
options.setTryRotate(true);
options.setTryDownscale(false);
options.setMaxNumberOfSymbols(100);
auto results = ZXing::ReadBarcodes(image, options);
if (results.empty())
{
std::cout << "QR code not found.\n";
}
else
{
std::cout << "--------------------------\n";
std::cout << "Detection count : " << results.size() << "\n\n";
int index = 1;
for (const auto& result : results)
{
if (!result.isValid())
continue;
std::cout << "--------------------------\n";
std::cout << "Code " << index << "\n";
std::cout << "Type : "
<< ZXing::ToString(result.format())
<< "\n";
std::cout << "Content : "
<< result.text()
<< "\n\n";
++index;
}
}
Gdiplus::GdiplusShutdown(gdiplusToken);
return 0;
}
なお、第3版時点で解析できる画像タイプはpngのみです。jpgも読み込むことはできますがjpgは圧縮時のノイズが多いためか、私の環境ではうまく解析できませんでした。
で、用意した画像はこれです。
QRCode以外のコードも書いてみました、内容は「Win32は死なズ。」という文字列をエンコードしています。ただしコードの形式によってはAsciiのみや、数字のみ、といった制限もありますので適時変えてあります。
実際に実行した結果は以下のとおり。
QrDecodeTest.exe TestQr.png
--------------------------
Detection count : 4
--------------------------
Code 1
Type : QR Code
Content : Win32は死なズ。
--------------------------
Code 2
Type : EAN-13
Content : 1234567890128
--------------------------
Code 3
Type : Code 39
Content : WIN32-SHINAZU-39M
--------------------------
Code 4
Type : Data Matrix
Content : WIN32 HA
なんか判別できてないコードもある気がするけど・・・、それは私が作ったテスト画像がショボいためです。
それよりも ZXing-C++ が素晴らしいライブラリだと言うことは分かる結果だと思います。
これから少しずつ調整していけば十分使えるツールを作ることもできると思います。
例えば、デスクトップのスクリーンショットを撮って、その中にQRコードが含まれているか判別し、あれはエンコードして内容を表示する。といったツールであれば比較的簡単に作れると思います。
最後はなんだか駆け足になってしまってすみません。
でも、Win32は死なズ。
では、また。

