画像を前処理すると、統計が簡単になります。あなたの場合、幅の広い水平線とそれに続く大津しきい値(統計的に最適)による形態学的クロージングにより、タスクがはるかに簡単になります。形態学的な開口部はここで興味深いものです。なぜなら、は特に紙の領域をはるかに軽くするからです。境界領域がぼやけている2つの例があります。つまり、明るい部分も含まれていますが、この手順が役に立たないわけではありません。その後は、列ごとと行ごとに合計し、平均と標準偏差に基づいて境界線を区切るだけです。値が以下の場合mean - x*stddev
、それからそれは紙の外にあります。このようにして、画像のトリミングに使用する用紙の左上隅と右下隅を定義できます。このようなコーナーを定義する最も簡単な方法は、見つかった合計を前後に直線的にトラバースし、以前の条件が満たされないときに停止することです。
あなたの画像でx
は、[-1.5、-1]の範囲で機能します(他の画像も同様に、私はそのあたりでテストしました)。クロージングオペレーターの横線のサイズを101ポイントに固定しました。結果は次のとおりです(比較のために必要な場合は、コーナー座標を含めることができます)。


問題は、指摘されているように、これらの画像の一部には、次の場合(紙に接続されている)のように白い境界線も含まれていることです。これを処理するには、画像がバイナリになった後、コンポーネントを切断することを期待して、形態学的な開口部を適用することを検討してください。大きな構造要素を使用できます。私は51x51のサイズのいずれかを使用しましたが、これは画像のサイズとしてはそれほど大きくありません。主な制限は、使用しているライブラリの実装です。実装が悪い場合は遅くなる可能性があります(特に、scipyには高速な実装がありません)。その後、最大のコンポーネントのみを保持し、通常どおりに続行します。

サンプルコード:
import sys
import numpy
import cv2 as cv
from PIL import Image, ImageOps, ImageDraw
from scipy.ndimage import morphology, label
img = ImageOps.grayscale(Image.open(sys.argv[1]))
im = numpy.array(img, dtype=numpy.uint8)
im = morphology.grey_closing(img, (1, 101))
t, im = cv.threshold(im, 0, 1, cv.THRESH_OTSU)
# "Clean noise".
im = morphology.grey_opening(im, (51, 51))
# Keep largest component.
lbl, ncc = label(im)
largest = 0, 0
for i in range(1, ncc + 1):
size = len(numpy.where(lbl == i)[0])
if size > largest[1]:
largest = i, size
for i in range(1, ncc + 1):
if i == largest[0]:
continue
im[lbl == i] = 0
col_sum = numpy.sum(im, axis=0)
row_sum = numpy.sum(im, axis=1)
col_mean, col_std = col_sum.mean(), col_sum.std()
row_mean, row_std = row_sum.mean(), row_sum.std()
row_standard = (row_sum - row_mean) / row_std
col_standard = (col_sum - col_mean) / col_std
def end_points(s, std_below_mean=-1.5):
i, j = 0, len(s) - 1
for i, rs in enumerate(s):
if rs > std_below_mean:
break
for j in xrange(len(s) - 1, i, -1):
if s[j] > std_below_mean:
break
return (i, j)
# Bounding rectangle.
x1, x2 = end_points(col_standard)
y1, y2 = end_points(row_standard)
#img.crop((x1, y1, x2, y2)).save(sys.argv[2]) # Crop.
result = img.convert('RGB')
draw = ImageDraw.Draw(result)
draw.line((x1, y1, x2, y1, x2, y2, x1, y2, x1, y1),
fill=(0, 255, 255), width=15)
result.save(sys.argv[2]) # Save with the bounding rectangle.