7

四角形が画像内で領域を接続している場合、どのように検出できますか?高度な方形検出(接続された領域で)

私はそれがうまく機能しなかった OpenCV C++/Obj-C: Advanced square detection

に記載の方法をテストしました。

いいアイデアはありますか?

squares that has Connected region

import cv2 
import numpy as np 

def angle_cos(p0, p1, p2): 
    d1, d2 = (p0-p1).astype('float'), (p2-p1).astype('float') 
    return abs(np.dot(d1, d2)/np.sqrt(np.dot(d1, d1)*np.dot(d2, d2))) 

def find_squares(img): 
    squares = [] 
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) 
    # cv2.imshow("gray", gray) 

    gaussian = cv2.GaussianBlur(gray, (5, 5), 0) 

    temp,bin = cv2.threshold(gaussian, 80, 255, cv2.THRESH_BINARY) 
    # cv2.imshow("bin", bin) 

    contours, hierarchy = cv2.findContours(bin, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE) 

    cv2.drawContours(gray, contours, -1, (0, 255, 0), 3) 

    #cv2.imshow('contours', gray) 
    for cnt in contours: 
     cnt_len = cv2.arcLength(cnt, True) 
     cnt = cv2.approxPolyDP(cnt, 0.02*cnt_len, True) 
     if len(cnt) == 4 and cv2.contourArea(cnt) > 1000 and cv2.isContourConvex(cnt): 
      cnt = cnt.reshape(-1, 2) 
      max_cos = np.max([angle_cos(cnt[i], cnt[(i+1) % 4], cnt[(i+2) % 4]) for i in xrange(4)]) 
      if max_cos < 0.1: 
       squares.append(cnt) 
    return squares 

if __name__ == '__main__': 
    img = cv2.imread('123.bmp') 

    #cv2.imshow("origin", img) 

    squares = find_squares(img) 
    print "Find %d squres" % len(squares) 
    cv2.drawContours(img, squares, -1, (0, 255, 0), 3) 
    cv2.imshow('squares', img) 

    cv2.waitKey() 

私はOpenCVの例では、いくつかの方法を使用しますが、結果は良くありません。

答えて

12

距離変換に基づく変換は、オブジェクトを分離します:

国境でのオブジェクトの処理

enter image description here

は常に問題があり、そして上部のピンクの長方形がある分離しないままになるように、多くの場合、廃棄されましたまったく問題ありません。

与えられたバイナリイメージでは、Distance Transform(DT)を適用し、Watershedのマーカーを取得できます。理想的には、地域の最小値/最大値を見つけるための準備関数があるだろうが、そこにはないので、我々はしきい値DTをどのようにできるかについてまともな推測をすることができる。 Watershedを使用してセグメント化することができるマーカーに基づいて、問題は解決されます。これで、矩形であるコンポーネントとそうでないコンポーネントを区別することができます。

import sys 
import cv2 
import numpy 
import random 
from scipy.ndimage import label 

def segment_on_dt(img): 
    dt = cv2.distanceTransform(img, 2, 3) # L2 norm, 3x3 mask 
    dt = ((dt - dt.min())/(dt.max() - dt.min()) * 255).astype(numpy.uint8) 
    dt = cv2.threshold(dt, 100, 255, cv2.THRESH_BINARY)[1] 
    lbl, ncc = label(dt) 

    lbl[img == 0] = lbl.max() + 1 
    lbl = lbl.astype(numpy.int32) 
    cv2.watershed(cv2.cvtColor(img, cv2.COLOR_GRAY2BGR), lbl) 
    lbl[lbl == -1] = 0 
    return lbl 


img = cv2.cvtColor(cv2.imread(sys.argv[1]), cv2.COLOR_BGR2GRAY) 
img = cv2.threshold(img, 0, 255, cv2.THRESH_OTSU)[1] 
img = 255 - img # White: objects; Black: background 

ws_result = segment_on_dt(img) 
# Colorize 
height, width = ws_result.shape 
ws_color = numpy.zeros((height, width, 3), dtype=numpy.uint8) 
lbl, ncc = label(ws_result) 
for l in xrange(1, ncc + 1): 
    a, b = numpy.nonzero(lbl == l) 
    if img[a[0], b[0]] == 0: # Do not color background. 
     continue 
    rgb = [random.randint(0, 255) for _ in xrange(3)] 
    ws_color[lbl == l] = tuple(rgb) 

cv2.imwrite(sys.argv[2], ws_color) 

上記の画像から、各コンポーネントの楕円をフィッティングして矩形を決定することができます。次に、測定値を使用して、コンポーネントが長方形かどうかを定義することができます。このアプローチは、完全に目に見える矩形で作業する可能性が高く、部分的に見えるものに対しては悪い結果をもたらす可能性があります。次の図は、適合楕円からの矩形がコンポーネントの面積の10%以内にある場合、コンポーネントが矩形であることを考慮した結果です。

enter image description here

# Fit ellipse to determine the rectangles. 
wsbin = numpy.zeros((height, width), dtype=numpy.uint8) 
wsbin[cv2.cvtColor(ws_color, cv2.COLOR_BGR2GRAY) != 0] = 255 

ws_bincolor = cv2.cvtColor(255 - wsbin, cv2.COLOR_GRAY2BGR) 
lbl, ncc = label(wsbin) 
for l in xrange(1, ncc + 1): 
    yx = numpy.dstack(numpy.nonzero(lbl == l)).astype(numpy.int64) 
    xy = numpy.roll(numpy.swapaxes(yx, 0, 1), 1, 2) 
    if len(xy) < 100: # Too small. 
     continue 

    ellipse = cv2.fitEllipse(xy) 
    center, axes, angle = ellipse 
    rect_area = axes[0] * axes[1] 
    if 0.9 < rect_area/float(len(xy)) < 1.1: 
     rect = numpy.round(numpy.float64(
       cv2.cv.BoxPoints(ellipse))).astype(numpy.int64) 
     color = [random.randint(60, 255) for _ in xrange(3)] 
     cv2.drawContours(ws_bincolor, [rect], 0, color, 2) 

cv2.imwrite(sys.argv[3], ws_bincolor) 
+0

+1 - 素敵な仕事。 –

+2

うん、本当にいいアプローチ、+1。画像フォーマットを正しいフォーマットにするのは恐ろしいことですが、ここで示した同じzip展開方法を使用していましたが、最近は移調やコピーよりも遅くなることがわかりました(重要です)。残念ながら、 (少なくとも私にとっては)opencvの例外を避けるためにコピーが必要なようです。 – fraxel

+0

違いはnumpyはデフォルトで '(y、x)'座標で動作し、OpenCVは '(x、y)'です。 @fraxel私はパフォーマンスを測定しませんでしたが、更新されたコードがその特定の点で優れている可能性があります。 – mmgp

2

解決方法1:

は、接続されたコンポーネントを削除するには、あなたのイメージを膨らませます。 検出されたコンポーネントの輪郭を検索します。何らかの尺度(例:比周囲/面積)を導入して矩形ではない輪郭を除去する。

このソリューションは、境界線に接続された矩形を検出しません。

解決方法2:

拡張は、接続されているコンポーネントを削除します。 輪郭を検索します。 輪郭を近似して点を減らしてください(矩形輪郭は4点です)。 等高線間の角度が90度であることを確認してください。 90度のない輪郭を削除します。

これは、境界線に接続された長方形の問題を解決するはずです。

1

次の3つの問題を抱えて:

  1. 矩形はそれらの多くがありますが非常に厳しい長方形(エッジは、多くの場合、やや湾曲している)
  2. ではありません。
  3. 頻繁に接続されています。

すべてあなたのrectsは、本質的に同じ大きさ(?)しており、大幅にオーバーラップしていないが、前処理は、それらを接続しているようです。

このような状況のために、私がしようとするだろうなアプローチがある:

  1. dilateあなたのイメージに数回(も@krzychにより示唆されるように) - これは、接続を削除、しかしわずかに小さいrectsになります。
  2. scipyをlabelfind_objectsにする - イメージ内の残りのブロブの位置とスライスを知るようになりました。
  3. minAreaRectを使用して、各矩形の中心、向き、幅、高さを確認します。

手順3を使用できます。ブロブが有効な矩形であるかどうか、その面積、寸法比、またはエッジへの近接度によってテストします。

これは、各ブロブが長方形であると仮定しているので、かなり良いアプローチです。minAreaRectは、最小包囲矩形のパラメータ。さらに必要なら絶対にhumomentsのようなものを使って各ブロブをテストすることができます。

ここで私が実際に提案したのは、赤で示された境界衝突の一致です。

enter image description here

コード:

import numpy as np 
import cv2 
from cv2 import cv 
import scipy 
from scipy import ndimage 

im_col = cv2.imread('jdjAf.jpg') 
im = cv2.imread('jdjAf.jpg',cv2.CV_LOAD_IMAGE_GRAYSCALE) 

im = np.where(im>100,0,255).astype(np.uint8) 
im = cv2.erode(im, None,iterations=8) 
im_label, num = ndimage.label(im) 
for label in xrange(1, num+1): 
    points = np.array(np.where(im_label==label)[::-1]).T.reshape(-1,1,2).copy() 
    rect = cv2.minAreaRect(points) 
    lines = np.array(cv2.cv.BoxPoints(rect)).astype(np.int) 
    if any([np.any(lines[:,0]<=0), np.any(lines[:,0]>=im.shape[1]-1), np.any(lines[:,1]<=0), np.any(lines[:,1]>=im.shape[0]-1)]): 
     cv2.drawContours(im_col,[lines],0,(0,0,255),1) 
    else: 
     cv2.drawContours(im_col,[lines],0,(255,0,0),1) 

cv2.imshow('im',im_col) 
cv2.imwrite('rects.png',im_col) 
cv2.waitKey() 

は私が@mmgpによって実証さWatersheddistanceTransformアプローチは、画像を分割するための明らかに優れていると思うが、この単純なアプローチは、ニーズに応じて、有効であることができます。流域を適用

+0

ありがとうございました、私はこの方法が速く流域法、その後だと思う、あなたはコードにいくつかのコメントを与えることができますか? 「ndimage.label」とはどういう意味ですか?また、 "np.array(np.where(im_label == label)[:: - 1])。T.reshape(-1,1,2).copy()"? aswerのためにありがとうございます〜 – Yang

+0

また、 "np.any(lines [:、0] <= 0)、np.any(lines [:、0]> = im.shape [1] -1)、np。 (行[:、1] <= 0)、np.any(行[:、1]> = im.shape [0] -1)])「わかりません。 – Yang

+1

@Yang - hey、確かにheresショット: 'ndimage.label(im)'は画像を分割するために使用されます:連結されていないすべての値は整数で順番に置き換えられ、新しいラベル画像 'im_label'が生成されます。 'np.where(im_label == label)'は、この新しいラベル付けされたイメージを受け取り、そのイメージのラベルに等しいすべてのピクセルのインデックスを返します。単一のブロブのすべてのインデックス値 - 一度に1つのラベル値のみを考慮して、ブロブを反復処理していることに注意してください。 '.T.reshape(-1,1,2).copy()'は、データを正しいフォーマットにして、minAreaRectによって受け入れられるようにするためのものです。 – fraxel

関連する問題