web-dev-qa-db-ja.com

プログラムで画像(テキストを保持)のすべての線と境界を削除する方法は何ですか?

Tesseract OCRを使用して画像からテキストを抽出しようとしています。現在、元の入力画像(下図)では、出力の品質は非常に低くなっています(約50%)。したがって、プログラムで(OpenCV、Image magickを使用して)image(keep texts)のすべての線と境界線を削除する方法はありますか?

元の画像: Original Image

期待されるイメージ: Expect Image

19
wind

OpenCVを使用せず、ターミナルでのImageMagickの1行だけを使用しますが、OpenCVでそれを実行する方法についてのアイデアが得られる場合があります。 ImageMagickは、ほとんどのLinuxディストリビューションにインストールされており、OSXおよびWindowsで使用できます。

コンセプトの核心は、各ピクセルがその左に隣接する100ピクセルとその右に隣接する100ピクセルの中央値に設定される新しい画像を作成することです。このようにして、黒である水平方向の隣接ピクセルが多いピクセル(つまり、水平方向の黒い線)は、出力イメージで白になります。次に、同じ処理を垂直方向に適用して、垂直線を削除します。

ターミナルに入力するコマンドは次のとおりです。

convert input.png                                                 \
   \( -clone 0 -threshold 50% -negate -statistic median 200x1 \)  \
   -compose lighten -composite                                    \
   \( -clone 0 -threshold 50% -negate -statistic median 1x200 \)  \
   -composite result.png

最初の行は、元の画像を読み込むことを示しています。

2行目は、いくつかの "aside-processing"を開始し、元の画像をコピーし、しきい値を設定して反転します。次に、両側のすべての隣接ピクセル100の中央値計算されます。

次に、3番目のラインは2番目のラインの結果を取得し、それを元の画像の上に合成し、各位置のピクセルの明るい方(つまり、水平ラインマスクが白くなっているピクセル)を選択します。

次の2行も同じことを行いますが、垂直線の場合は垂直に向けられます。

結果は次のようになります:

enter image description here

このように元の画像と異なる場合は、次のようになります。

convert input.png result.png -compose difference -composite diff.png

enter image description here

もう少し線を削除したい場合は、実際に差分画像を少しぼかして、元の画像に適用できます。もちろん、フィルターの長さやしきい値などで遊ぶこともできます。

14
Mark Setchell

ImageMagickでこれを行うより良い方法があります。

線の形状を特定して削除する

ImageMagickには、形状の形態と呼ばれるきちんとした機能があります。これを使用して、表の線のような形状を識別し、それらを削除できます。

一発ギャグ

convert in.png                              \
-type Grayscale                             \
-negate                                     \
-define morphology:compose=darken           \
-morphology Thinning 'Rectangle:1x80+0+0<'  \
-negate                                     \
out.png

説明

  • convert in.png:画像を読み込みます。
  • -type Grayscale:ImageMagickがそれがグレースケール画像であることを認識していることを確認してください。
  • -negate:画像のカラーレイヤーを反転します(グレースケールを設定することで既に適切に調整されています)。線と文字は白、背景は黒になります。
  • -define morphology:compose = darken:形態によって識別された領域が暗くなることを定義します。
  • -morphology Thinning 'Rectangle:1x80 + 0 + 0 <'は、線の形状を識別するために使用される1px x 80pxの長方形カーネルを定義します。このカーネルが白い形(私たちnegateの色を思い出してください)の内側または内側に収まる場合にのみ、暗くなります。 <フラグを使用すると、回転できます。
  • -negate:もう一度色を反転します。これで文字は再び黒になり、背景は白になります。
  • out.png:生成される出力ファイル。

結果の画像

申し込み後

convert in.png -type Grayscale -negate -define morphology:compose=darken -morphology Thinning 'Rectangle:1x80+0+0<' -negate out.png

これは出力画像でした:

Output image after one liner was applied

観察

  • 大きい方の文字サイズよりも大きい長方形カーネルサイズを選択して、長方形が文字内に収まらないようにする必要があります。
  • 一部の小さな点線と小さな表のセル分割はまだ残っていますが、それは80ピクセルよりも小さいためです。
  • この手法のメリットは、他のユーザーがここで提案した中央値のピクセル色差アプローチよりも優れた文字を維持できることと、多少の乱雑さにもかかわらず、表の行を削除することで非常に優れた結果が得られることです。
13
luizv

同じ問題に直面した。そして、私はより論理的な解決策があり得ると感じています(参照: 表の境界線を抽出

//assuming, b_w is the binary image
inv = 255 - b_w    
horizontal_img = new_img
vertical_img = new_img

kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (100,1))
horizontal_img = cv2.erode(horizontal_img, kernel, iterations=1)
horizontal_img = cv2.dilate(horizontal_img, kernel, iterations=1)


kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,100))
vertical_img = cv2.erode(vertical_img, kernel, iterations=1)
vertical_img = cv2.dilate(vertical_img, kernel, iterations=1)

mask_img = horizontal_img + vertical_img
no_border = np.bitwise_or(b_w, mask_img)
7

誰も完全なOpenCVソリューションを投稿していないので、ここに簡単なアプローチがあります

  1. バイナリイメージを取得します。イメージをロードし、グレースケールに変換し、そして 大津のしきい値

  2. 水平線を削除します。_cv2.getStructuringElement_ 次に 輪郭を見つける で水平形状のカーネルを作成します。 = _cv2.drawContours_ で行を削除します

  3. 垂直線を削除します。同じ操作を行いますが、垂直形状のカーネルを使用します


画像を読み込み、グレースケールに変換し、次に 大津のしきい値 でバイナリ画像を取得します

_image = cv2.imread('1.png')
result = image.copy()
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
_

enter image description here

次に、水平カーネルを作成して cv2.getStructuringElement() で水平線を検出し、 cv2.findContours() で輪郭を見つけます。水平線を削除するには、 cv2.drawContours() を使用して、各水平輪郭を白で塗りつぶします。これにより、水平線が効果的に「消去」されます。検出された水平線は緑色です

_# Remove horizontal lines
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (40,1))
remove_horizontal = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, horizontal_kernel, iterations=2)
cnts = cv2.findContours(remove_horizontal, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    cv2.drawContours(result, [c], -1, (255,255,255), 5)
_

enter image description here

同様に、垂直カーネルを作成して、垂直線を削除し、輪郭を見つけ、各垂直輪郭を白で塗りつぶします。検出された垂直線は緑色で強調表示されています

_# Remove vertical lines
vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,40))
remove_vertical = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, vertical_kernel, iterations=2)
cnts = cv2.findContours(remove_vertical, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    cv2.drawContours(result, [c], -1, (255,255,255), 5)
_

enter image description here

水平線と垂直線の両方を白で埋めた後、これが結果です

enter image description here


注:イメージによっては、カーネルサイズの変更が必要になる場合があります。たとえば、長い水平線をキャプチャするには、水平カーネルを_(40, 1)_から_(80, 1)_に増やす必要がある場合があります。太い水平線を検出したい場合は、カーネルの幅を増やして_(80, 2)_とすることができます。さらに、cv2.morphologyEx()を実行するときに、反復回数を増やすことができます。同様に、垂直カーネルを変更して、多かれ少なかれ垂直線を検出できます。カーネルのサイズを増減する場合、キャプチャする行数が多かれ少なかれ、トレードオフがあります。繰り返しますが、それはすべて入力画像によって異なります

完全性のための完全なコード

_import cv2

image = cv2.imread('1.png')
result = image.copy()
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]

# Remove horizontal lines
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (40,1))
remove_horizontal = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, horizontal_kernel, iterations=2)
cnts = cv2.findContours(remove_horizontal, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    cv2.drawContours(result, [c], -1, (255,255,255), 5)

# Remove vertical lines
vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,40))
remove_vertical = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, vertical_kernel, iterations=2)
cnts = cv2.findContours(remove_vertical, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    cv2.drawContours(result, [c], -1, (255,255,255), 5)

cv2.imshow('thresh', thresh)
cv2.imshow('result', result)
cv2.imwrite('result.png', result)
cv2.waitKey()
_
6
nathancy

Sobel/Laplacian/CannyのEdge検出アルゴリズムを使用し、Houghの変換を使用してOpenCVのLineを識別し、それらを白く色付けしてLineを削除できます。

laplacian = cv2.Laplacian(img,cv2.CV_8UC1) # Laplacian OR
edges = cv2.Canny(img,80,10,apertureSize = 3) # canny Edge OR
# Output dtype = cv2.CV_8U # Sobel
sobelx8u = cv2.Sobel(img,cv2.CV_8U,1,0,ksize=5)
# Output dtype = cv2.CV_64F. Then take its absolute and convert to cv2.CV_8U
sobelx64f = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=5)
abs_sobel64f = np.absolute(sobelx64f)
sobel_8u = np.uint8(abs_sobel64f)

# Hough's Probabilistic Line Transform 
minLineLength = 900
maxLineGap = 100
lines = cv2.HoughLinesP(edges,1,np.pi/180,100,minLineLength,maxLineGap)
for line in lines:
    for x1,y1,x2,y2 in line:
        cv2.line(img,(x1,y1),(x2,y2),(255,255,255),2)

cv2.imwrite('houghlines.jpg',img)
4

Leptonicaと Lept4j が必要です。

テストのプロジェクトのソースコードでこれを行う方法の例があります ここ:LineRemovalTest.Java

入力:

enter image description here

出力:

enter image description here

2
delkant

私はアイデアを持っています。ただし、完全に水平線と垂直線がある場合にのみ機能します。最初にこの画像で2値化を実行できます(まだの場合)。次に、いくつかのしきい値を超える一連の黒いピクセルがあるかどうかを確認しながら、同時に画像の各行を反復処理するコードを記述します。たとえば、100番目のピクセルから150番目のピクセルまでの行に連続した一連の黒い点がある場合、これらのピクセルを白にします。すべての水平線を見つけたら、同じようにして垂直線を取り除くことができます。

ここでの例では、黒のピクセルシーケンスが100番目のピクセルから始まり、150番目のピクセルで終わると考えています。151番目のピクセルに別の黒のピクセルがある場合、そのピクセルも追加する必要があるためです。つまり、行を完全に見つけるようにしてください。

この質問を解決した場合はお知らせください)

1
Kanat