ケースはもちろん、ストレッチ付きです。 電話に領収書の写真があれば、電話が領収書を認識する必要はまったくありません。写真は、どこで何を取得する必要があるかをすでに明確に示しています。 一方、このようなソフトウェアがどのように機能するかという例に興味があります。また、必要なすべての商品を倉庫マップに配置することで生活を簡素化できます。 つまり 4〜5枚の小切手の写真があるため、それらを認識して、セルフサービスウェアハウス周辺の走行ルートの地図を作成できます。 よく、彼らは私達の考えを実行する1つの理由を思いついた。 2番目の理由は色です。 個人的に、私はあなたが例えば黒い胸の小切手を撮るという事実に出くわしました。写真を撮りたい小切手が近くにあるという事実に注意を払っていません。 小切手を認識すると、小切手にある(したがってセルフサービス倉庫に配置されている)製品のユーザーにすぐにプレビューを表示できます。 さて、2番目の理由は、とてつもなく大きなものでした。
では、何をする必要がありますか?
- 画像でイケアチェックを見つけます。
- 小切手を特定し、小切手からのデータを認識のために準備します。
- データを認識します。
ツールを決定します。 を使用して画像を処理します OpenCV 、信頼の認識 Tesseract 。 シェルを書く NodeJS
OpenCVの選択は明らかです。 ほとんどの画像処理アルゴリズムを自分の手で書き換えることができるプロでさえ、ほとんどの場合OpenCVを使用します。 このライブラリは、「 オープンソースコンピュータービジョンライブラリ 」の略です。 理論的には純粋ですが、gd + imagickのケースに対する解決策を思い付くことができます。 しかし、これは倒錯です。
OpenCVは、カメラで小切手を見つけることと、小切手の認識を準備することを担当します。
かつて、Tesseractを使用してYandex captcha、rambler、googleを認識していました。 このライブラリがプレーンテキストの認識に簡単に対処できることは間違いありません。 正直なところ、ocrの「素人」であるため、オープンソースでこれほど強力なものは他にありません。
NodeJSは完全に個人的な選択です。 現時点では、JSで多くのことを行っており、このような言葉があれば、この言語は純粋に構文的に単純です。
アルゴリズムの速度と品質を装うわけではありません。コードをきれいにするためには、コンピュータービジョンをすばやく思い出し、知識を更新し、プロジェクトを実装する必要がありました。 したがって、編集や批判は歓迎しますが、ヒステリーなしでお願いします。 まあ、繰り返しますが、私は自分自身と同じ「老人」のための練習を共有します。そうすることで、若いプログレのパーティー(私は17歳です...)
追われた。 私が作成しました githubのカブ 、すべてのコードは実際にあります。 もう少し理解します。 / public / images /のカブには、IKeevの小切手の4枚の写真があります。 さまざまな角度から、さまざまなサイズの写真を撮ることができました。 テストに必要なもの。
1.画像の検出を確認します。
OpenCVライブラリは非常に強力であり、この記事で説明する機能に限定されるものではありません。 最初に、GD + IMagickが問題を解決できることを予約したのは、何の理由もありませんでした。 特に、プロはほとんどの場合、 テンプレートマッチング手法を使用してチェックを検出しますが、より簡単な方法で進めます。 幸いなことに、IKEAはこれに非常に役立ちます。
最初に知っておく必要があるのは、商品の場所に関する情報を含むチェックが常に赤であることです。 さて、これに基づいて構築します。
// convert to HSV process.convertHSVscale(); // find only red process.inRange([0,100,100], [10,255,255]);
画像(この場合はプロセス変数に読み込まれます)をHSVに変換し、指定されたチャンネル間で画像を探します。 私はこれがどのように機能するのかほとんど分かりませんが、画像とその色域から特定の色を選択する機会を探して、開発者による記事と公式文書の 2つの資料に出会いました。 HSVパレットで[0,100,100]〜[10,255,255]のマトリックスを使用していることがコードから明らかです。 色の強調表示に関する公式ドキュメントから、一般的なルールは非常に単純であることがわかります。 [h-10、100、100]と[h + 10、255,255]を使用します。hは必要な色です。 colorizer.orgサービスを使用してインデックスを取得しました。
何が起こったのか見てみましょう。
さて、赤とその色合いを選びました。 合計で、彼らは小切手、私の手、カーペットの印刷上の赤い正方形を見つけました。 findContourメソッドを使用すると、「カツレツからハエ」を簡単に分離できます。 画像で「境界線」を見つけます。
var possibleContour = []; // var contours = process.findContours(); for (var i = 0; i < contours.size(); i++) { if (contours.area(i) < 20000) continue; // var arcLength = contours.arcLength(i, true); contours.approxPolyDP(i, 0.05 * arcLength, true); // (« …») switch(contours.cornerCount(i)) { case 4: contourImg.drawContour(contours, i, [0,255,0]); // 4- break; default: contourImg.drawContour(contours, i, [0,0,255]); // } if (contours.cornerCount(i) ==4) { // 4- . , , . possibleContour.push(pointOrder(contours.points(i))); } }
行に注意してください
var arcLength = contours.arcLength(i, true); contours.approxPolyDP(i, 0.05 * arcLength, true); // (« …»)
近似ポリDP法を使用して、検出された輪郭の「角を滑らかにする」ことで、「ノイズ」を排除します。 詳細については、 公式ドキュメントのページをご覧ください。
findContourがうまく機能することも注目に値しますが、見つかったポイントの順序は予測できない場合があります。 右視点の場合、ポイントの順序を左上、右上、右下、左下の形式に保つことが重要です。
タスクは単純で、左上のポイントを見つけるなどのように思えます。 実際には、これは配列に対するハンディキャップの束に変わりました。 彼はグーグル検索を開始し、コンピュータービジョンとopencvに関する素晴らしいブログに出会いました。 開発者は、これらの非常に「極端な」ものを探して、私たちが必要とする職を探しました。 特に、x + y座標の最小合計は常に左上のポイントに等しくなり、最大量は右下に等しくなります。 同様に、最大間隔xyは右上のポイントに等しく、最小間隔は左下のポイントに等しくなります。 pointOrderメソッドを取得する
pointOrder: function (point) { var ordered = []; var sum = []; for (var x in point) { sum[x] = point[x].x+point[x].y; } ordered[0] = point[sum.indexOf(_.min(sum))]; ordered[2] = point[sum.indexOf(_.max(sum))]; var diff = []; for (var x in point) { diff[x] = point[x].x-point[x].y; } ordered[1] = point[diff.indexOf(_.max(diff))]; ordered[3] = point[diff.indexOf(_.min(diff))]; return ordered; }
視点に優先順位が必要です。 水平および垂直のポイントは事前にわかっており(これらは0/0、maxWidth / 0、maxWidth / maxHeightおよび0 / maxHeightです)、画像を反転させないために、パスのポイントをこの順序で転送する必要があります。
輪郭を準備しました。次のステップは、遠近法を揃えることです。
var warpImg = []; for (var x in possibleContour) { var point = possibleContour[x]; var maxWidth = 0; var maxHeight = 0; var tmp = 0; // width/height if (pointWidth(point[0], point[1]) >pointWidth(point[3], point[2])) { maxWidth = Math.round(pointWidth(point[0], point[1])); } else { maxWidth = Math.round(pointWidth(point[3], point[2])); } if (pointWidth(point[0], point[3]) >pointWidth(point[1], point[2])) { maxHeight = Math.round(pointWidth(point[0], point[3])); } else { maxHeight = Math.round(pointWidth(point[1], point[2])); } // var tmpWarpImg = img.copy(); var srcWarp = [point[0].x, point[0].y, point[1].x, point[1].y, point[2].x, point[2].y, point[3].x, point[3].y]; var dstWarp = [0, 0, maxWidth, 0, maxWidth, maxHeight, 0, maxHeight]; var perspective = tmpWarpImg.getPerspectiveTransform(srcWarp, dstWarp); tmpWarpImg.warpPerspective(perspective, maxWidth, maxHeight, [255, 255, 255]); warpImg.push(tmpWarpImg); }
画像からわかるように、2つの回路が見つかります。 必要なのは2つ目、「うるさい」2つ目です。
2.チェックを特定し、データを準備します。
まず、「左」の輪郭を反対から除外します。 チェックをすぐに識別します。 チェックについて何を知っていますか? 確かに、形状は長方形で、テキストとデータを含む3つの長方形があります。 それらの上に構築します。 すでに類推して見つかった画像で、私たちは輪郭を探していますが、今回は3つの長方形の輪郭があるそれらの見つかった領域だけを選択します。
// filter wrapped img var trueWarpImg = []; for (var x in warpImg) { var warpedImg = warpImg[x].copy(); // convert to HSV warpedImg.convertHSVscale(); // find only red warpedImg.inRange([0,100,100], imgProc.[10,255,255]); var possibleContour = []; var contourImg = warpImg[x].copy(); var contours = warpedImg.findContours(); for (var i = 0; i < contours.size(); i++) { if (contours.area(i) < 2000 || contours.area(i) > 20000) continue; // var arcLength = contours.arcLength(i, true); contours.approxPolyDP(i, 0.05 * arcLength, true); switch(contours.cornerCount(i)) { case 4: contourImg.drawContour(contours, i, [0,255,0]); break; default: contourImg.drawContour(contours, i, [0,0,255]); } if (contours.cornerCount(i) ==4) { possibleContour.push(pointOrder(contours.points(i))); } } // , 3 , if (possibleContour.length == 3) { var trueContour = []; var width = []; var tmpContour = _.cloneDeep(possibleContour); // . , . for (var x2 in tmpContour) { width.push(tmpContour[x2][1].x - tmpContour[x2][0].x); } var maxIndex = width.indexOf(_.max(width)); trueContour[0] = tmpContour[maxIndex]; var left = []; for (var x2 in tmpContour) { if (x2 == maxIndex) continue; left.push(tmpContour[x2][0].x); } trueContour[1] = tmpContour[left.indexOf(_.min(left))]; trueContour[2] = tmpContour[left.indexOf(_.max(left))]; trueWarpImg.push({img: warpImg[x], contour: trueContour}); } }
ああ、まあ、一般的には、コードの重複を気にせずに、タスクをすばやく検討することがタスクだと警告しました。 はい。プロジェクトがプロ向けに準備されていない場合、これを行うのは好きではありません。 率直に言って、いくつかの場所の多くの本番プロジェクトでは、初心者が読みやすいようにコードの重複を残しています。 等高線はランダムな順序で見つけることができることに注意してください。それらの順序、商品はどこ、列はどこ、場所はどこかを知ることが重要です。
出力で、IKEAチェックを見つけ、認識のためにデータを抽出したのは彼+であることを確認しました。
3.データを認識します。
tesseractの寿命を単純化するために、画像を切り取り、赤を分離して(bw画像を作成するために)増やします。 この増加により、tesseractは少し遅くなりますが、認識率は1桁増加します。
// ocr img for (var x in labelImg) { var label = labelImg[x]; for (var x2 in label) { var labelLine = label[x2]; // convert to HSV labelLine.convertHSVscale(); // find only red labelLine.inRange([0,100,100], [10,255,255]); // labelLine.gaussianBlur([5,5]); // , . labelLine.resize(labelLine.width()*3,labelLine.height()*3); Tesseract.recognize(labelLine.toBuffer(), { lang: 'eng', tessedit_char_whitelist: '0123456789.' }) .progress(function(msg){/*console.log('tesseract', msg)*/}) .catch(function(msg){/*console.log('tesseract', msg)*/}) .then(function(result){console.log('"', result.text.trim(), '"')}); } }
コンソールに入る
全然悪くない。
次のパートでは、コードをモバイルプラットフォームに転送し、モバイルクロスプラットフォームを作成します。 アプリケーションにウェアハウスカードを入れ、IKEAベースをロードします。もちろん、すべてをオフラインで動作させます。
さて、リンクを複製する プロジェクトのカブに。
使用材料:
- 画像の色検出に関する優れた資料
- 画像内の特定の色を見つけることに関する公式文書
- HSVでの作業を含む、便利なカラー操作サービス
- OpenCVで回路を見つける
- 見つかった輪郭の角を滑らかにし、ノイズを除去します
- 指定されたものから左上、右上、右下、左下の点を検索します
有用な資料: