この記事は確かに興味深く有用です。 「深い満足感で」私たちは、ABBYYが私たちと同じ数学的アルゴリズムを使用しており、文書の境界を決定する精度が大幅に低下することなくいくつかの詳細を慎重に省略していることに気付きました。
記事を読んだ後、読者の中には「写真で見つかった文書をさらにどうするか」という合理的な質問があったと思う。チェシャ猫アリスの言葉に答える:「どこに来たい?」データ、その後、認識システムのタスクを最大化する必要があります。 これを行うには、まず、ドキュメントのすべての写真を「手作業で」惨めにする、遠近感の歪みを修正する必要があります。 この問題が解決しない場合、データを認識しようとすると、captchaを認識しようとする場合と同等の結果が得られる場合があります。 小さな価格表でのキャプチャに対するマシンインテリジェンスの勝利の「信者」は、viましい規則性でフリーランスサイトに登場します。 信じる人は祝福されますが、私たちは今ではそうではありません。
そのため、この記事では、ABBYYからバトンを取り出し、最小限のコストで画像で特定したプリズム状の文書(できれば科学のおかげでABBYYに感謝)を、できれば保存して、初期の割合。 五角形や楕円形の文書などのエキゾチックなケースは考慮しませんが、この質問は興味深いものです。
有望な歪みの歪みの問題は、ALANIS Softwareが予想される側からではなく、ALANIS Softwareの前に発生しました。 つまり、モバイル開発に特化していないということです。 しかし、Canon EOSデジタル一眼レフカメラ(こんにちは、写真家!)をベースにした惑星スキャナー用のスキャンおよび画像処理システムを開発しているお客様は、ある時点で、そのような機能を武器庫に入れたいと考えていました。 さらに、完成したカメラ画像の処理ではなく、LiveViewプレビュー段階でのビデオストリームの調整についてでした。 ただし、私たちが開発したソリューションは、すでに取得したドキュメントの修正モードでも同様に機能します。
与えられた:
- 長方形の文書のゆがんだ画像
- 画像内のドキュメントの輪郭
チャレンジ:
最短の方法でドキュメントを元の形式に戻す
チャレンジ(ロシアの同等のものはどういうわけか発生しません):
- 元の文書の割合がわからない
- 文書が置かれている平面までの距離はわかりません
- 焦点を合わせることができる参照オブジェクトはありません(たとえば、レンズに落ちた正しい正方形)
解決策:
したがって、問題全体を解決するために、2つの別々の問題に分けることを提案します。
- 実際、スキャンされた画像上で文書の輪郭のゆがみを見つけます(おそらく、ABBYYの記事を読んでいない人のために、この問題をもう一度取り上げます)。
- 整列されたドキュメントを取得するために、元の歪んだアウトラインをマップする必要があるドキュメントの正しい比率を決定します。
もちろん、自転車を発明することもできますが、いくつかはまだ成功していますが、私たちはより簡単な方法でOpenCVツールキットを使用しました。 C#Wrapper OpenCVSharpを使用して、主に.NET環境で作業しています。 OpenCVSharpは、Visual StudioのNugetパッケージとしても利用できます。 「これですべて」(c)、それを使用します。
次の画像の遠近法画像を修正する問題を解決する際の主な興味深い点を考慮してください。

1.提示された画像の輪郭を見つけるには、干渉する可能性のある小さな部分を取り除く必要があります。 これは、以前に画像をグレーの濃淡に変換した、低電力のガウスによる「ぼかし呪文」を適用することで実行できます。
imgSource.CvtColor(imgGrayscale, ColorConversion.BgrToGray);
imgSource.Smooth(imgSource, SmoothType.Gaussian, 15);
上記のチェーンを適用した結果、それが起こりました(眼鏡を外せば、ほぼ同じ効果が得られます。したがって、「近視は病気ではなく、すべてを不必要に取り除き、世界をより美しくすることを目的とする知的画像処理です!」):

2.次に、画像を白黒にする必要があります。
imgSource.Threshold(imgSource, 0, 255, ThresholdType.Binary | ThresholdType.Otsu);

3.結果の画像で、ドキュメントのアウトラインを簡単に見つけることができます。 最大の外部輪郭を探します。 OpenCVSharpには、見つかった画像のアウトラインをすべてリストできる優れたCvContourScannerクラスがあります。 Linqを使用すると、これらのアウトラインをエリアごとに並べ替えて、最初のアウトラインを取得することができます。
using (var storage = new CvMemStorage())
using (var scanner = new CvContourScanner(image, _storage, CvContour.SizeOf, ContourRetrieval.External, ContourChain.ApproxSimple))
{
var largestContour = scanner.OrderBy(contour => Math.Abs(contour.ContourArea())).FirstOrDefault();
}
見つかったアウトラインを描画すると、次の画像が表示されます。

4.万歳! サーキットを見つけました! ただし、ほとんど表示されない場合があります-すべてのコーナーポイントの座標を正確に知る必要があります-文書の側面の交点です。 明らかに、これらの点の座標を見つけるには、見つかった輪郭の側面を直線方程式で記述することが望ましいです。 OpenCVはこれをどのように支援できますか? とても簡単です! ハフ変換を使用するツールがあります。 このメソッドを前の手順で取得した画像に「キャスト」します。
var lineSegments = imgSource.HoughLines2(storage, HoughLinesMethod.Probabilistic, 1, Math.PI / 180.0, 70, 100, 1).ToArray();
, 4 , , ! 100, 200, . , , , ( «» OpenCV). , - , , : , :
var verticalSegments = segments
.Where(s => Math.Abs(s.P1.X - s.P2.X) < Math.Abs(s.P1.Y - s.P2.Y))
.ToArray();
var horizontalSegments = segments
.Where(s => Math.Abs(s.P1.X - s.P2.X) >= Math.Abs(s.P1.Y - s.P2.Y))
.ToArray();
, «» – ; – . , , :
, . , :
var corners = horizontalSegments
.SelectMany(sh => verticalSegments
.Select(sv => sv.LineIntersection(sh))
.Where(v => v != null)
.Select(v => v.Value))
// exclude points which is out of image area
.Where(c => new CvRect(0, 0, imgSource.Width, imgSource.Height).Contains(c))
.ToArray();
:
– ). OpenCVSharp:
contour = contour.ApproxPoly(CvContour.SizeOf, storage, ApproxPolyMethod.DP, contour.ArcLength() * 0.02, true);
, ! , - :
5. , . , – , . , , . , – - , . , , .
, , , 100% . , , , , , .
, , . : . . , , :
:
, , . !
- . , ( 10, – , – ):
, «» . - , , . - , , :
:
deltaX, deltaY – , ;
targetWidth, targetHeight – ;
topWidth, bottomWidth, leftHeight, rightHeight – .
:
, :
. , .
, «» , «» , , - …
, - . , . , Canon. «» « » LiveView, .
, . youtube , , . !
var lineSegments = imgSource.HoughLines2(storage, HoughLinesMethod.Probabilistic, 1, Math.PI / 180.0, 70, 100, 1).ToArray();
, 4 , , ! 100, 200, . , , , ( «» OpenCV). , - , , : , :
var verticalSegments = segments
.Where(s => Math.Abs(s.P1.X - s.P2.X) < Math.Abs(s.P1.Y - s.P2.Y))
.ToArray();
var horizontalSegments = segments
.Where(s => Math.Abs(s.P1.X - s.P2.X) >= Math.Abs(s.P1.Y - s.P2.Y))
.ToArray();
, «» – ; – . , , :
, . , :
var corners = horizontalSegments
.SelectMany(sh => verticalSegments
.Select(sv => sv.LineIntersection(sh))
.Where(v => v != null)
.Select(v => v.Value))
// exclude points which is out of image area
.Where(c => new CvRect(0, 0, imgSource.Width, imgSource.Height).Contains(c))
.ToArray();
:
– ). OpenCVSharp:
contour = contour.ApproxPoly(CvContour.SizeOf, storage, ApproxPolyMethod.DP, contour.ArcLength() * 0.02, true);
, ! , - :
5. , . , – , . , , . , – - , . , , .
, , , 100% . , , , , , .
, , . : . . , , :
:
, , . !
- . , ( 10, – , – ):
, «» . - , , . - , , :
:
deltaX, deltaY – , ;
targetWidth, targetHeight – ;
topWidth, bottomWidth, leftHeight, rightHeight – .
:
, :
. , .
, «» , «» , , - …
, - . , . , Canon. «» « » LiveView, .
, . youtube , , . !