写真の紙の角を検出するアルゴリズム


97

写真の請求書/領収書/用紙のコーナーを検出する最良の方法は何ですか?これは、OCRの前に、後続の遠近補正に使用されます。

私の現在のアプローチは:

RGB>グレー>しきい値付きキャニーエッジ検出>膨張(1)>小さいオブジェクトの削除(6)>境界オブジェクトのクリア>凸領域に基づいて大きいブログを選択 > [コーナー検出-実装されていません]

このタイプのセグメンテーションを処理するには、より堅牢な「インテリジェント」/統計的アプローチが必要だと思います。トレーニングの例はあまりありませんが、おそらく100枚の画像をまとめることができます。

幅広いコンテキスト:

私はプロトタイプにmatlabを使用しており、OpenCVおよびTesserect-OCRでシステムを実装する予定です。これは、この特定のアプリケーションで解決する必要がある多くの画像処理問題の最初のものです。だから私は自分のソリューションを展開し、画像処理アルゴリズムに慣れるようにしています。

アルゴリズムで処理したいサンプル画像は次のとおりです:挑戦したい場合は、大きな画像がhttp://madteckhead.com/tmpにあります。

ケース1
(ソース:madteckhead.com

ケース2
(ソース:madteckhead.com

ケース3
(ソース:madteckhead.com

ケース4
(ソース:madteckhead.com

最良の場合、これは次のようになります。

ケース1-キャニー
(ソース:madteckhead.com

ケース1-ポストキャニー
(ソース:madteckhead.com

ケース1-最大のブログ
(ソース:madteckhead.com

ただし、他の場合は簡単に失敗します。

ケース2-キャニー
(ソース:madteckhead.com

ケース2-ポストキャニー
(ソース:madteckhead.com

ケース2-最大のブログ
(ソース:madteckhead.com

素晴らしいアイデアをありがとうございます。大好き!

編集:ハフ変換の進行状況

Q:ハフラインをクラスター化してコーナーを見つけるアルゴリズムは何ですか?回答からのアドバイスに従って、ハフ変換を使用し、ラインを選択し、フィルタリングすることができました。私の現在のアプローチはかなり粗雑です。私は、請求書が常に画像とずれて15度未満になると仮定しました。これが当てはまる場合、行に対して妥当な結果が得られます(以下を参照)。しかし、コーナーを推定するためにラインをクラスター化(または投票)するための適切なアルゴリズムについて完全に確信はありません。ハフ線は連続していません。そして、ノイズの多い画像では、平行な線が存在する可能性があるため、線の起点メトリックからの何らかの形または距離が必要です。何か案は?

ケース1 ケース2 ケース3 ケース4
(ソース:madteckhead.com


1
はい、ケースの約95%で機能しました。それ以来、時間不足のためにコードを棚上げする必要がありました。いつかフォローアップを投稿します。緊急のサポートが必要な場合は、遠慮なく依頼してください。フォローアップが不十分で申し訳ありません。この機能の作業に戻りたいです。
Nathan Keller

ネイサン、あなたがどうやってそれをやったのかについてのフォローアップを投稿していただけませんか?用紙の角/外形を認識して、同じポイントでスタックしました。私はあなたとまったく同じ問題に遭遇したので、解決策に非常に興味があります。
tim 2014年

6
今、この記事内の画像のすべての404
ChrisF

回答:


28

私は今年初めにこれに取り組んでいたマーティンの友人です。これは私の初めてのコーディングプロジェクトであり、ちょっと急いで終わったので、コードにはいくつかのエラーが必要です...デコード...私がすでにあなたが見てきたことからいくつかのヒントをあげます。明日の休日にコードを並べ替えます。

最初のヒント、OpenCVそしてpythonすばらしいです。できるだけ早くそれらに移動してください。:D

小さなオブジェクトやノイズを削除する代わりに、キャニー拘束を下げてより多くのエッジを受け入れ、最大の閉じた輪郭を見つけます(OpenCV findcontour()でいくつかの単純なパラメーターを使用して、CV_RETR_LIST)。白い紙の上ではまだ苦労するかもしれませんが、間違いなく最良の結果を提供していました。

以下のためにHoughline2()変換、との試みCV_HOUGH_STANDARDと対照的にCV_HOUGH_PROBABILISTIC、それはあげるローシータを値、極座標内の行を定義します。次にグループ化することができ、それらに一定の許容範囲内の行。

私のグループ化はルックアップテーブルとして機能しました。ハフ変換から出力された各行に対して、ローとシータのペアを提供します。これらの値がテーブル内の値のペアの5%以内である場合、それらは破棄され、5%を超えている場合、新しいエントリがテーブルに追加されました。

その後、平行線または線間の距離の分析をはるかに簡単に行うことができます。

お役に立てれば。


こんにちはダニエル、参加してくれてありがとう。私はあなたが近づくのが好きです。そのルートイムは現時点で良い結果を得ています。長方形を検出するOpenCVの例もありました。結果に対して何らかのフィルタリングを行う必要がありました。あなたが言っていたように、白の白はこの方法で検出するのは難しいです。しかし、それはハフよりもシンプルでコストのかからないアプローチでした。私は実際にはハフアプローチをアルゴリズムから除外し、ポリ近似を行いました。opencvの正方形の例を見てください。ハフ投票の実施をお願いします。事前にありがとう、ネイサン
ネイサンケラー

私はこのアプローチに問題がありました。将来の参考のためにもっと良いものを考案できる場合は、解決策を投稿します
Anshuman Kumar

@AnshumanKumar私はこの質問について本当に助けが必要です、私を助けてくれませんか?stackoverflow.com/questions/61216402/...
カルロス・ディエゴ・

19

私の大学の学生グループが最近、iPhoneアプリ(およびpython OpenCVアプリ)をデモしました。これは、まさにこれを行うために書かれたものです。私が覚えているように、ステップは次のようなものでした:

  • 紙のテキストを完全に削除するための中央値フィルター(これはかなり良い照明の白い紙の手書きのテキストであり、印刷されたテキストでは機能しない可能性があり、非常にうまく機能しました)。その理由は、コーナーの検出がはるかに簡単になるからです。
  • 線のハフ変換
  • ハフ変換アキュムレータ空間のピークを見つけ、画像全体に各線を引きます。
  • ラインを分析し、互いに非常に近く、同様の角度にあるものをすべて削除します(ラインを1つにまとめます)。ハフ変換は離散的なサンプル空間で機能するため、完全ではないため、これが必要です。
  • ほぼ平行で、他のペアと交差するラインのペアを見つけ、どのラインが四角形を形成しているかを確認します。

これはかなりうまく機能しているようで、紙や本の写真を撮り、コーナー検出を実行してから、画像内のドキュメントをほぼリアルタイムで平面にマッピングできました(実行する単一のOpenCV関数がありました)マッピング)。動作しているのを見たとき、OCRはありませんでした。


マーティンの素晴らしいアイデアをありがとう。私はあなたのアドバイスを取り入れて、ハフ変換アプローチを実装しました。(上記の結果を参照してください)。交差を見つけるために線を外挿する強力なアルゴリズムを決定するのに苦労しています。行数は少なく、誤検知もいくつかあります。行をマージして破棄するのに最適な方法について何かアドバイスはありますか?生徒が興味を持っている場合は、連絡を取るように奨励してください。アルゴリズムをモバイルプラットフォームで実行する際の彼らの経験を聞きたいです。(それが私の次の目標です)。あなたのアイデアに感謝します。
Nathan Keller

1
線のHTは2番目の画像を除くすべてでうまく機能しているようですが、アキュムレータで開始値と終了値のしきい値許容値を定義していますか?HTは実際には開始位置と終了位置を定義せず、y = mx + cのmとcの値を定義します。ここを参照-これはデカルト座標ではなくアキュムレータの極座標を使用していることに注意してください。このように、ラインをcでグループ化し、次にmでグループ化してラインを間引くことができます。また、ラインがイメージ全体に広がっていると想像すると、より便利な交差が見つかります。
マーティンフット

@MartinFoot私はこの質問について本当に助けが必要です、私を助けてくれませんか?stackoverflow.com/questions/61216402/...
カルロス・ディエゴ・

16

少し実験した後、これが私が思いついたものです:

import cv, cv2, numpy as np
import sys

def get_new(old):
    new = np.ones(old.shape, np.uint8)
    cv2.bitwise_not(new,new)
    return new

if __name__ == '__main__':
    orig = cv2.imread(sys.argv[1])

    # these constants are carefully picked
    MORPH = 9
    CANNY = 84
    HOUGH = 25

    img = cv2.cvtColor(orig, cv2.COLOR_BGR2GRAY)
    cv2.GaussianBlur(img, (3,3), 0, img)


    # this is to recognize white on white
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(MORPH,MORPH))
    dilated = cv2.dilate(img, kernel)

    edges = cv2.Canny(dilated, 0, CANNY, apertureSize=3)

    lines = cv2.HoughLinesP(edges, 1,  3.14/180, HOUGH)
    for line in lines[0]:
         cv2.line(edges, (line[0], line[1]), (line[2], line[3]),
                         (255,0,0), 2, 8)

    # finding contours
    contours, _ = cv2.findContours(edges.copy(), cv.CV_RETR_EXTERNAL,
                                   cv.CV_CHAIN_APPROX_TC89_KCOS)
    contours = filter(lambda cont: cv2.arcLength(cont, False) > 100, contours)
    contours = filter(lambda cont: cv2.contourArea(cont) > 10000, contours)

    # simplify contours down to polygons
    rects = []
    for cont in contours:
        rect = cv2.approxPolyDP(cont, 40, True).copy().reshape(-1, 2)
        rects.append(rect)

    # that's basically it
    cv2.drawContours(orig, rects,-1,(0,255,0),1)

    # show only contours
    new = get_new(img)
    cv2.drawContours(new, rects,-1,(0,255,0),1)
    cv2.GaussianBlur(new, (9,9), 0, new)
    new = cv2.Canny(new, 0, CANNY, apertureSize=3)

    cv2.namedWindow('result', cv2.WINDOW_NORMAL)
    cv2.imshow('result', orig)
    cv2.waitKey(0)
    cv2.imshow('result', dilated)
    cv2.waitKey(0)
    cv2.imshow('result', edges)
    cv2.waitKey(0)
    cv2.imshow('result', new)
    cv2.waitKey(0)

    cv2.destroyAllWindows()

完璧ではありませんが、少なくともすべてのサンプルで機能します。

1 2 3 4


4
私は同様のプロジェクトに取り組んでいます。コードの上で実行すると、「cvという名前のモジュールはありません」というエラーが表示されます。私はOpen CV 2.4バージョンをインストールし、インポートcv2は完全に機能しています。
Navneet Singh

このコードが機能するように更新してもらえますか? pastebin.com/PMH5Y0M8それはちょうど私に黒いページを与えます。
the7erm

あなたは、javaに次のコードを変換する方法についてのアイデアを持っています: for line in lines[0]: cv2.line(edges, (line[0], line[1]), (line[2], line[3]), (255,0,0), 2, 8) # finding contours contours, _ = cv2.findContours(edges.copy(), cv.CV_RETR_EXTERNAL, cv.CV_CHAIN_APPROX_TC89_KCOS) contours = filter(lambda cont: cv2.arcLength(cont, False) > 100, contours) contours = filter(lambda cont: cv2.contourArea(cont) > 10000, contours)
aurelianr

バヌアン、私はこの質問について本当に助けが必要です、私を助けてくれませんか?stackoverflow.com/questions/61216402/...
カルロス・ディエゴ・

9

エッジ検出から開始する代わりに、コーナー検出を使用できます。

Marvin Frameworkは、この目的のためにMoravecアルゴリズムの実装を提供します。論文のコーナーを出発点として見つけることができます。Moravecのアルゴリズムの出力の下:

ここに画像の説明を入力してください


4

また、MSERを使用できます、Sobel演算子の結果に対して(最大安定極値領域)を、画像の安定領域を見つけることもできます。MSERによって返される各領域に対して、凸包とポリ近似を適用して、次のようなものを取得できます。

しかし、この種の検出は、常に最良の結果を返すとは限らない単一の画像以上のライブ検出に役立ちます。

結果


1
事前に感謝し、このおそらくいくつかのコードのための束をいくつかの詳細を共有することができます
モンティ

cv2.CHAIN_APPROX_SIMPLEで、アンパックするには値が多すぎるというエラーが表示されます。何か案が?サンプルとして1024 * 1024の画像を使用しています
Praveen

1
おかげですべての、ちょうど現在のOpenCVのブランチにおける構文の変化を考え出しanswers.opencv.org/question/40329/...を
Praveenさん

MSERはblobを抽出するためのものではありませんか?私が試してみたところ、ほとんどのテキストのみが検出されました
Anshuman Kumar

3

エッジ検出後、ハフ変換を使用します。次に、それらのポイントをラベル付きのSVM(サポートベクターマシン)に配置します。例に滑らかな線がある場合、SVMは例の必要な部分と他の部分を分割するのに問題はありません。SVMに関する私のアドバイスは、接続性や長さなどのパラメーターを設定します。つまり、ポイントが接続されていて長い場合、それらはレシートのラインになる可能性があります。その後、他のすべてのポイントを排除できます。


こんにちはアレス、あなたのアイデアをありがとう!ハフ変換を実装しました(上記を参照)。誤検知と連続していない線を考慮して、コーナーを見つけるための確実な方法を見つけることができません。他にアイデアはありますか?SVMのテクニックを見てからしばらく経ちました。これは監視付きアプローチですか?トレーニングデータはありませんが、生成することはできます。SVMについてもっと学びたいと思っているので、このアプローチを探求することに興味があります。リソースをお勧めできますか。敬具。ネイサン
ネイサンケラー2011

3

ここに、C ++を使用した@Vanuanのコードがあります。

cv::cvtColor(mat, mat, CV_BGR2GRAY);
cv::GaussianBlur(mat, mat, cv::Size(3,3), 0);
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Point(9,9));
cv::Mat dilated;
cv::dilate(mat, dilated, kernel);

cv::Mat edges;
cv::Canny(dilated, edges, 84, 3);

std::vector<cv::Vec4i> lines;
lines.clear();
cv::HoughLinesP(edges, lines, 1, CV_PI/180, 25);
std::vector<cv::Vec4i>::iterator it = lines.begin();
for(; it!=lines.end(); ++it) {
    cv::Vec4i l = *it;
    cv::line(edges, cv::Point(l[0], l[1]), cv::Point(l[2], l[3]), cv::Scalar(255,0,0), 2, 8);
}
std::vector< std::vector<cv::Point> > contours;
cv::findContours(edges, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_TC89_KCOS);
std::vector< std::vector<cv::Point> > contoursCleaned;
for (int i=0; i < contours.size(); i++) {
    if (cv::arcLength(contours[i], false) > 100)
        contoursCleaned.push_back(contours[i]);
}
std::vector<std::vector<cv::Point> > contoursArea;

for (int i=0; i < contoursCleaned.size(); i++) {
    if (cv::contourArea(contoursCleaned[i]) > 10000){
        contoursArea.push_back(contoursCleaned[i]);
    }
}
std::vector<std::vector<cv::Point> > contoursDraw (contoursCleaned.size());
for (int i=0; i < contoursArea.size(); i++){
    cv::approxPolyDP(Mat(contoursArea[i]), contoursDraw[i], 40, true);
}
Mat drawing = Mat::zeros( mat.size(), CV_8UC3 );
cv::drawContours(drawing, contoursDraw, -1, cv::Scalar(0,255,0),1);

行変数定義はどこにありますか?std :: vector <cv :: Vec4i>行である必要があります。
2014

@CanÜrekあなたは正しいです。std::vector<cv::Vec4i> lines;プロジェクトのグローバルスコープで宣言されています。
GBF_Gabriel 2014

1
  1. ラボスペースに変換

  2. kmeansセグメント2クラスターを使用する

  3. 次に、クラスターのいずれかで輪郭またはハフを使用します(中央)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.