検出できないとき非常に遅いです
既に2値化に近い画像はマスクされてしまい読み取れませんでした・・・
QRを読みたいだけなら直接ZXingに投げてしまうのが一番速そうです
準備
環境はVisualStudio2017。
nugetパッケージマネージャで以下コマンドを実行しました。
Install-Package OpenCvSharp3-AnyCPU -Version 3.4.1.20180319
前提として画像のピントは合っているものとします。
概要
こちらを参考にC#で書いて見ました。
OpenCVでQRコード検出器を書く
ここでは大まかに
- 画像から正方形を検出
- ファインダパターンを検出
- QRコードの輪郭を検出
と言ったことが紹介されています。
これをC#で書いてみて使ってみた結果、
少し修正したい点がありましたので以下の点を追加、変更しました。
追加
2値化時にマスクをかけて真っ白部分を処理しないようにしました。
元々読み込んだ画像をグレースケールにして2値化~
とやってましたが、映像と無関係の真っ白があると2値化時点で閾値がずれてしまう問題が起こりました。
傾きの画像を作るときに図形を回転させて余白部分が真っ白になっていたわけですが、この真っ白の領域に閾値判定が引っ張られてしまい、QRコードの輪郭を検出できなくなりました。
よって真っ白部分(ついでに真っ黒部分も)を除去しておきます。
変更
正方形検出をBoundingRectでなくMinAreaRectに変更しました。
QRコードの画像を45度程度傾けるとBoundingRectでは検出が難しいのでは?
縦横比と面積比の関係が固定値では上手く導けないように感じました。
MinAreaRectで傾いたRectのWidthとHeightが取れそうなのでこちらを使ったほうが単純かな?と思いました。
コード
private Bitmap GetQRTrim(string filename)
{
//イメージの読み込み
var loadImage = Cv2.ImRead(filename, ImreadModes.Color);
//HSVカラーに変換
var hsvImage = loadImage.CvtColor(ColorConversionCodes.BGR2HSV, 3);
//処理対象だけを抜き出す為のマスク
//HSVcolor [0] 色相
//HSVcolor [1] 彩度
//HSVcolor [2] 輝度
//手動で画像加工した時に真っ白BGR(255,255,255)、真っ黒(0,0,0) がいると閾値がずれてしまったので除去する。
var mask = hsvImage.InRange(new Scalar(5, 5, 5), new Scalar(250, 250, 250));
//マスクを通した結果画像を取得
var res = new Mat();
Cv2.BitwiseAnd(hsvImage, hsvImage, res, mask);
res = res.CvtColor(ColorConversionCodes.HSV2BGR, 3);
//グレースケール変換
var gray = res.CvtColor(ColorConversionCodes.BGR2GRAY);
//2値化
var binImg = gray.Threshold(0, 255, ThresholdTypes.Otsu);
//画像内の輪郭を抽出
var contours = new Mat[] { };
var hierarchy = new Mat();
binImg.FindContours(out contours, hierarchy, RetrievalModes.List, ContourApproximationModes.ApproxNone);
//正方形っぽいものを抽出する
var squares = contours.Where(a =>
{
//BoundingRectだと傾いたQRコードが検出できない?
//var rect = a.BoundingRect();
//var area = Math.Abs(Cv2.ContourArea(a));
//var area_ratio = area / (rect.Width * rect.Height);
//var rect_ratio = ((rect.Size.Width < rect.Size.Height) ?
// (double)rect.Size.Width / (double)rect.Size.Height :
// (double)rect.Size.Height / (double)rect.Size.Width);
//return area > 10 && area_ratio > 0.7 && rect_ratio > 0.9; // 正方形っぽいものを探して
var rect = a.MinAreaRect();
var area = Math.Abs(Cv2.ContourArea(a));
var rect_ratio = ((rect.Size.Width < rect.Size.Height) ?
rect.Size.Width / rect.Size.Height :
rect.Size.Height / rect.Size.Width);
//この辺の数字は適宜調整してください
return area > 10 && !double.IsNaN(rect_ratio) && !double.IsInfinity(rect_ratio) && rect_ratio > 0.7;
});
if (squares.Count() < 1)
{
//正方形らしき輪郭は見つからなかったので
return null;
}
//自らを含め3つの矩形を内包した矩形を探す = ファインダパターンの検出
//黒、白、黒の3つの輪郭の最外郭が検出できる
var finderPattarn = squares.Where(a => squares.Count(b => a.BoundingRect().Contains(b.BoundingRect())) == 3).ToArray();
if (finderPattarn.Count() != 3)
{
//ファインダパターンは3つのコーナーに存在するはず
//3つ無い時はQRコードが画像中に無いと考える。
return null;
}
//ファインダパターンの頂点を含む矩形 = QRコードの輪郭
var qrCodeContour = Cv2.MinAreaRect(finderPattarn.Select(a => a.MinAreaRect().Points()).SelectMany(b => b));
//検出した輪郭を描画する為のイメージ(確認用のため不要です)
var imgDetect = Cv2.ImRead(filename, ImreadModes.Color);
//検出した輪郭を描画する。
Cv2.DrawContours(imgDetect, contours, -1, Scalar.Gold);
//正方形らしきものの輪郭を描画する。
Cv2.DrawContours(imgDetect, squares, -1, Scalar.Blue);
//ファインダパターンの輪郭を描画する。
Cv2.DrawContours(imgDetect, finderPattarn, -1, Scalar.Red);
//QRコードの輪郭を描画する
for (int i = 0; i < 4; i++)
Cv2.Line(imgDetect, qrCodeContour.Points()[i], qrCodeContour.Points()[(i + 1) % 4], Scalar.LimeGreen);
//結果の表示
Cv2.ImShow("contuours", imgDetect[qrCodeContour.BoundingRect()]);
//トリミングしたビットマップを取得
return OpenCvSharp.Extensions.BitmapConverter.ToBitmap(loadImage[qrCodeContour.BoundingRect()]);
}