問題
OpenCV のQRCodeDetector
クラスをラップするラッパーライブラリ(具体的にはOpenCVSharpとOpenCV-Pythonで確認)が特定のQRコードに対してQRCodeDetector::detectAndDecode
でエラーを吐くor文字化けなどの問題を起こす件について。
他のQRコードリーダでは正しく読めたのでかなり困惑しました。
あんまり情報が無くて半日つぶしたので残しとく。
例
例えばこの画像で確認できる。
OpenCV-Pythonの言い分
import cv2
img=cv2.imread("qr.png")
qcd = cv2.QRCodeDetector()
result = qcd.detectAndDecode(img)
print(result)
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x82 in position 0: invalid start byte
OpenCVSharpの応答
using var image = Cv2.ImRead(name);
using var detector = new QRCodeDetector();
var result = detector.DetectAndDecode(image, out var area);
Console.WriteLine(result);
コンソールでの文字列出力
?±?eQR?R?[?h?AOpenCV?C?A???a?≪?¢?n???e???E
内部データ表現
C#の文字列表現はUTF-16です。一方文字列の内部データ全文字1バイトしか使ってなかったのでその部分だけ抽出して並べるとこうなります。
82-B1-82-EA-51-52-83-52-81-5B-83-68-82-C6-4F-70-65-6E-43-56-82-C7-82-C1-82-BF-82-AA-88-AB-82-A2-82-F1-82-BE-82-EB-82-A4-82-CB
結論
OpenCV-Pythonのエラーが大体原因書いてくれてますけど
これはOpenCVから返されたデータがUTF-8に則ってないために起こるみたいです。
もうちょっと根本的に言うと対象のQRコードがちょっとおかしかった場合に起こるみたい。多分
原因の考察
QRコードには4種類のデータ格納モードがあり、通常日本語文字列を格納する場合は漢字モードというモードにShift-JISエンコードで格納すると規定されています。
で、OpenCVは漢字モードのデータに対しては Shift-JIS -> UTF-8 の変換かませてstd::string
で返すっぽいです。一方8bitバイトモードという格納モードの場合は読み取ったバイト列そのまんまstd::string
にのせて返すみたい。
(8bitバイトモードは公式の推奨だとJIS X 0201などなのでAsciiとほぼ同じ)
それで例の画像のように8bitバイトモードでShift-JISエンコードなされている くそ迷惑 斬新なQRコードを読み取ろうとした場合、
各ラッパーはUTF-8の文字列がOpenCVから返ってくることを期待してバイト列を各々の環境の文字列表現に変換しようとするけど中身はUTF-8じゃなくてShift-JISそのまんまの列なので...
ってことらしい。
ちなみに他のモードは
データ格納モードは他に数値モードと英数字モードがあるみたいですが、それぞれどうなるんでしょう?
めんどくさいので確認してませんがOpenCVはいい感じに文字列化するんでしょうか。
英数字モードはさすがにそのまま文字列化してくれそうですが、数値モードに対しては数値を文字列化したものを返すのか?それとも数値自体をバイト列で返すのかな。前者の方が都合がいいんですが...
何が悪いのか
直接的には漢字モードを使わずに8bitバイトモードでShift-JISエンコードされたデータを格納しているQRコードが悪い。OpenCVとラッパー達は一応規定に従っただけ
でもこれWeb上のある生成ツール使って出力した画像なんですが。それでも問題のあるコードのようですね
このQRコードは一部のQRコードリーダーやライブラリでは正常にデコードできるようですが(例えばZxingはデコードできるっぽい)、こうなってくると逆にどうして他のツールは正常に処理できるのかの方が気になります。
文字コード推定でもしてるんでしょうか
今んとこ対処法は思いつかないです。OpenCV-Pythonはエラー吐くし、OpenCVSharpはエラー吐かないまでも返ってきた文字列が不当なUTF-8デコードの産物であることを直接的に判断する方法がないので。
一応OpenCVSharpの方はもう一回Shift-JISデコードを噛ませると読めるようにはなりましたが、これは対症療法に過ぎないし...
まとめ
QRコード読み取りでこのような問題が起こったときは一応QRコードの方がフォーマットを間違えている可能性も考慮に入れてもいいかもしれません。
たとえ他のソフトでは読み取れたとしても
余談(愚痴)
QRコードのデータ内部表現の違いが原因なのは早々に検討がついてたんですけど意外とQRコードのデータモードを教えてくれるツールってないんですね。
お陰様でQRコードを人力で解読する羽目にあいました。もうやりたくないです。ちょっと楽しかったけど