opencvを使用して画像に存在するすべてのテキストの場所を取得する


11

テキスト(数字とアルファベット)を含むこの画像があります。この画像に存在するすべてのテキストと数字の場所を取得したい。また、すべてのテキストも抽出したいと思います。

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

画像の座標とすべてのテキスト(数字とアルファベット)を取得するにはどうすればよいですか。たとえば10B、44、16、38、22Bなど


あなたのテンソルフローのバージョンは何ですか?バージョンが2.1の場合は、2.0をインストールしてみてください
gellezzz

1
悪い質問に賞金を投げるのは良い習慣ではありません。あなたはこれを行う方法に関する知識を示さなかったので、あなたは単にいくつかの担当者と引き換えに完全なソリューションをコーディングするように開発者を誘惑しようとしているように見えます。そのため完璧な答えが見つかるとは思いませんが、時間を割いてお金を払えば、フリーランスのウェブサイトでより良いソリューションを手に入れることができると思います。
karlphillip

@karlphillip申し訳ありませんが、私は初心者なので、何か始める必要がありますよね?これを手伝ってくれませんか
プルキットバトナガール

回答:


13

ここでは、形態学的操作を使用してテキスト以外の輪郭を除外する潜在的なアプローチを示します。アイデアは:

  1. バイナリイメージを取得します。画像、グレースケール、大津のしきい値の読み込み

  2. 水平線と垂直線を削除します。を使用して水平および垂直のカーネルを作成しcv2.getStructuringElement、次に行を削除しますcv2.drawContours

  3. 対角線、円オブジェクト、曲線輪郭を削除します。輪郭領域cv2.contourArea と輪郭近似cv2.approxPolyDP を使用してフィルタリングし、テキスト以外の輪郭を分離します

  4. テキストのROIとOCRを抽出します。Pytesseractを使用して、輪郭を見つけてROIをフィルターし、次にOCRを フィルターします。


緑でハイライトされた水平線を削除

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

縦線を削除

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

さまざまなテキスト以外の輪郭(対角線、円形オブジェクト、曲線)を削除

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

検出されたテキスト領域

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

import cv2
import numpy as np
import pytesseract

pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe"

# Load image, grayscale, Otsu's threshold
image = cv2.imread('1.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
clean = thresh.copy()

# Remove horizontal lines
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15,1))
detect_horizontal = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, horizontal_kernel, iterations=2)
cnts = cv2.findContours(detect_horizontal, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    cv2.drawContours(clean, [c], -1, 0, 3)

# Remove vertical lines
vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,30))
detect_vertical = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, vertical_kernel, iterations=2)
cnts = cv2.findContours(detect_vertical, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    cv2.drawContours(clean, [c], -1, 0, 3)

cnts = cv2.findContours(clean, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    # Remove diagonal lines
    area = cv2.contourArea(c)
    if area < 100:
        cv2.drawContours(clean, [c], -1, 0, 3)
    # Remove circle objects
    elif area > 1000:
        cv2.drawContours(clean, [c], -1, 0, -1)
    # Remove curve stuff
    peri = cv2.arcLength(c, True)
    approx = cv2.approxPolyDP(c, 0.02 * peri, True)
    x,y,w,h = cv2.boundingRect(c)
    if len(approx) == 4:
        cv2.rectangle(clean, (x, y), (x + w, y + h), 0, -1)

open_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2,2))
opening = cv2.morphologyEx(clean, cv2.MORPH_OPEN, open_kernel, iterations=2)
close_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,2))
close = cv2.morphologyEx(opening, cv2.MORPH_CLOSE, close_kernel, iterations=4)
cnts = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    x,y,w,h = cv2.boundingRect(c)
    area = cv2.contourArea(c)
    if area > 500:
        ROI = image[y:y+h, x:x+w]
        ROI = cv2.GaussianBlur(ROI, (3,3), 0)
        data = pytesseract.image_to_string(ROI, lang='eng',config='--psm 6')
        if data.isalnum():
            cv2.rectangle(image, (x, y), (x + w, y + h), (36,255,12), 2)
            print(data)

cv2.imwrite('image.png', image)
cv2.imwrite('clean.png', clean)
cv2.imwrite('close.png', close)
cv2.imwrite('opening.png', opening)
cv2.waitKey()

最初にこれらの行を削除することをお勧めします。
karlphillip

文字の一部も削除するのは悪い考え...
Walter Tross

8

では、別の解決策を紹介します。私はあなたがPythonで作業していることを知っています-私はC ++で作業します。私はあなたにいくつかのアイデアを与えます、そしてもし望むなら、あなたが望むなら、あなたはこの答えを実装することができるでしょう。

主なアイデアは、前処理をまったく使用せず(少なくとも初期段階では使用しない)、代わりに各ターゲットキャラクターに焦点を当て、いくつかのプロパティを取得し、これらのプロパティに従ってすべてのblob をフィルター処理することです。

次の理由により、前処理を使用しないようにしています:1)フィルターと形態学的ステージがblobの品質を低下させる可能性があり、2)ターゲットblobが主に利用できるいくつかの特性を示すように見える:主にアスペクト比面積

確認してみてください。数字と文字はすべて、幅よりも背が高いように見えます…さらに、それらは特定の面積値内で変化するように見えます。たとえば、「大きすぎる」または「大きすぎるオブジェクトを破棄したいとします。

これは、事前に計算された値に該当しないものはすべてフィルタリングするという考え方です。文字(数字と文字)を調べ、最小値、最大値、最小アスペクト比(ここでは、高さと幅の比率)を用意しました。

アルゴリズムに取り組みましょう。画像を読み取り、半分のサイズにサイズ変更することから始めます。画像が大きすぎます。グレースケールに変換し、otsuを介してバイナリイメージを取得します。以下が擬似コードです。

//Read input:
inputImage = imread( "diagram.png" );

//Resize Image;
resizeScale = 0.5;

inputResized = imresize( inputImage, resizeScale );

//Convert to grayscale;
inputGray = rgb2gray( inputResized );

//Get binary image via otsu:
binaryImage = imbinarize( inputGray, "Otsu" );

涼しい。この画像で作業します。すべての白いblobを調べて、「プロパティフィルター」を適用する必要があります。統計で接続されたコンポーネントを使用して、各blobをループし、その面積とアスペクト比を取得します。C++では、これは次のように行われます。

//Prepare the output matrices:
cv::Mat outputLabels, stats, centroids;
int connectivity = 8;

//Run the binary image through connected components:
int numberofComponents = cv::connectedComponentsWithStats( binaryImage, outputLabels, stats, centroids, connectivity );

//Prepare a vector of colors  color the filtered blobs in black
std::vector<cv::Vec3b> colors(numberofComponents+1);
colors[0] = cv::Vec3b( 0, 0, 0 ); // Element 0 is the background, which remains black.

//loop through the detected blobs:
for( int i = 1; i <= numberofComponents; i++ ) {

    //get area:
    auto blobArea = stats.at<int>(i, cv::CC_STAT_AREA);

    //get height, width and compute aspect ratio:
    auto blobWidth = stats.at<int>(i, cv::CC_STAT_WIDTH);
    auto blobHeight = stats.at<int>(i, cv::CC_STAT_HEIGHT);
    float blobAspectRatio = (float)blobHeight/(float)blobWidth;

    //Filter your blobs

};

次に、プロパティフィルターを適用します。これは、事前に計算されたしきい値との比較にすぎません。次の値を使用しました。

Minimum Area: 40  Maximum Area:400
MinimumAspectRatio:  1

forループ内で、現在のblobプロパティをこれらの値と比較します。テストが陽性の場合、ブロブを「ペイント」します。forループの内側で続行:

    //Filter your blobs

    //Test the current properties against the thresholds:
    bool areaTest =  (blobArea > maxArea)||(blobArea < minArea);
    bool aspectRatioTest = !(blobAspectRatio > minAspectRatio); //notice we are looking for TALL elements!

    //Paint the blob black:
    if( areaTest || aspectRatioTest ){
        //filtered blobs are colored in black:
        colors[i] = cv::Vec3b( 0, 0, 0 );
    }else{
        //unfiltered blobs are colored in white:
        colors[i] = cv::Vec3b( 255, 255, 255 );
    }

ループの後、フィルターされた画像を作成します。

cv::Mat filteredMat = cv::Mat::zeros( binaryImage.size(), CV_8UC3 );
for( int y = 0; y < filteredMat.rows; y++ ){
    for( int x = 0; x < filteredMat.cols; x++ )
    {
        int label = outputLabels.at<int>(y, x);
        filteredMat.at<cv::Vec3b>(y, x) = colors[label];
    }
}

そして…それだけです。あなたが探しているものに似ていないすべての要素をフィルタリングしました。アルゴリズムを実行すると、次の結果が得られます。

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

さらに、結果をより視覚化するために、ブロブの境界ボックスを見つけました。

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

ご覧のように、いくつかの要素は誤検出されています。「プロパティフィルター」を調整して、探している文字をより適切に識別できます。少しの機械学習を含むより深い解決策は、「理想的な特徴ベクトル」の構築、ブロブからの特徴の抽出、および類似性測定を介した両方のベクトルの比較を必要とします。結果を改善するために後処理を適用することもできます...

何であれ、あなたの問題はささいなことでも簡単に拡張できることでもありません。私はただあなたにアイデアを与えています。うまくいけば、ソリューションを実装できるようになります。


同じプログラムをPythonに変換できる可能性があります
Pulkit Bhatnagar

@PulkitBhatnagarはい、もちろんです。あなたはしっかりとつかまって、数分で完璧なポートを準備します
eldesgraciado

?? あなたがそれをしたので、私はあなたに賞金を授与できます
プルキットバトナガール

ああそう。私はひどく申し訳ありません、私の先生、私はいくつかの問題に遭遇しましたが、改宗はうまくやっています。ただ待つだけ。THX。
eldesgraciado

それが嫌味かもしれないと思ったことはありません。
Pulkit Bhatnagar

4

1つの方法は、スライディングウィンドウを使用することです(高価です)。

画像内の文字のサイズを決定し(すべての文字は画像に表示されているのと同じサイズです)、ウィンドウのサイズを設定します。検出のためにテッセラクトを試してください(入力画像には前処理が必要です)。ウィンドウが文字を連続して検出した場合は、ウィンドウの座標を保存します。座標をマージして、キャラクターの領域を取得します。


私は100bountyが答えだと思う
ヒマンシュPoddar
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.