7

nếu hình vuông đã kết nối khu vực trong hình ảnh, làm thế nào tôi có thể phát hiện chúng.Phát hiện hình vuông nâng cao (có vùng được kết nối)

Tôi đã thử nghiệm phương pháp nêu tại OpenCV C++/Obj-C: Advanced square detection

Nó không làm việc tốt.

Bất kỳ ý tưởng hay nào?

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() 

tôi sử dụng một số phương pháp trong ví dụ opencv, nhưng kết quả là không tốt.

Trả lời

12

Áp dụng một đầu nguồn Transform dựa trên cách chuyển đổi sẽ tách các đối tượng:

enter image description here

Xử lý đối tượng tại biên giới phải lúc nào cũng có vấn đề, và thường bị loại bỏ, do đó hình chữ nhật màu hồng ở phía trên trái không tách ra là Không phải là một vấn đề gì cả.

Cho hình ảnh nhị phân, chúng tôi có thể áp dụng Chuyển đổi khoảng cách (DT) và từ đó có được điểm đánh dấu cho lưu vực đầu nguồn. Lý tưởng nhất là sẽ có một chức năng sẵn sàng cho việc tìm kiếm minima/maxima trong khu vực, nhưng vì nó không có ở đó, chúng ta có thể đoán được cách chúng ta có thể ngưỡng DT. Dựa trên các điểm đánh dấu, chúng tôi có thể phân đoạn bằng cách sử dụng lưu vực đầu nguồn và vấn đề được giải quyết. Bây giờ bạn có thể lo lắng về việc phân biệt các thành phần là hình chữ nhật từ những thành phần không có.

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) 

Từ hình trên bạn có thể xem xét các dấu chấm lửng trong mỗi thành phần để xác định hình chữ nhật. Sau đó, bạn có thể sử dụng một số phép đo để xác định xem thành phần đó có phải là hình chữ nhật hay không. Cách tiếp cận này có cơ hội lớn hơn để làm việc cho các hình chữ nhật có thể nhìn thấy hoàn toàn, và có khả năng sẽ tạo ra các kết quả xấu cho các phần hiển thị một phần. Hình ảnh sau đây cho thấy kết quả của cách tiếp cận như vậy xem xét rằng một thành phần là một hình chữ nhật nếu hình chữ nhật từ hình elip được trang bị là trong vòng 10% diện tích của thành phần.

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 - Làm tốt lắm. –

+2

yep, đúng, cách tiếp cận thực sự tốt đẹp, +1. Rất khó để đưa các chỉ mục hình ảnh vào đúng định dạng, tôi đã sử dụng phương pháp giải nén zip giống như bạn thấy ở đây, nhưng gần đây tôi nhận ra rằng nó có thể chậm hơn rất nhiều so với chuyển đổi và sao chép (nên tốc độ là quan trọng ..) Không may, sao chép dường như được yêu cầu để tránh một ngoại lệ opencv (ít nhất là đối với tôi) .. – fraxel

+0

Sự khác biệt là các công việc được mặc định theo mặc định với các coords '(y, x)', và OpenCV mong đợi '(x, y)'. @fraxel Tôi không đo lường hiệu suất, nhưng có khả năng mã được cập nhật tốt hơn ở điểm cụ thể đó. – mmgp

2

Giải pháp 1:

giãn hình ảnh của bạn để xóa các thành phần được kết nối. Tìm đường nét của các thành phần được phát hiện. Loại bỏ các đường nét không phải là hình chữ nhật bằng cách giới thiệu một số biện pháp (tỷ lệ chu vi/diện tích).

Giải pháp này sẽ không phát hiện hình chữ nhật được kết nối với đường viền.

Giải pháp 2:

giãn để xóa các thành phần được kết nối. Tìm đường nét. Đường viền gần đúng để giảm điểm của chúng (đối với đường viền hình chữ nhật phải là 4 điểm). Kiểm tra xem góc giữa các đường bao là 90 độ. Loại bỏ các đường nét không có 90 độ.

Điều này sẽ giải quyết vấn đề với hình chữ nhật được kết nối với đường viền.

1

Bạn có ba vấn đề:

  1. Các hình chữ nhật không phải là hình chữ nhật rất nghiêm ngặt (các cạnh thường hơi cong)
  2. Có rất nhiều trong số họ.
  3. Chúng thường được kết nối.

Dường như tất cả các rects của bạn về cơ bản có cùng kích thước (?) Và không trùng lặp nhiều, nhưng quá trình xử lý trước đã kết nối chúng.

Đối với tình huống này cách tiếp cận tôi sẽ cố gắng là:

  1. dilate hình ảnh của bạn một vài lần (cũng như đề xuất bởi @krzych) - điều này sẽ loại bỏ các kết nối, nhưng dẫn đến dễ phân biệt hơi nhỏ.
  2. Sử dụng scipy để labelfind_objects - Bây giờ bạn biết vị trí và phần cắt cho mọi đốm màu còn lại trong hình ảnh.
  3. Sử dụng minAreaRect để tìm trung tâm, hướng, chiều rộng và chiều cao của mỗi hình chữ nhật.

Bạn có thể sử dụng bước 3.để kiểm tra xem các đốm màu là một hình chữ nhật hợp lệ hay không, bởi khu vực của nó, tỷ lệ kích thước hoặc gần với cạnh ..

Đây là một cách tiếp cận tốt đẹp, như chúng ta giả sử mỗi đốm là một hình chữ nhật, vì vậy minAreaRect sẽ tìm thấy các thông số cho hình chữ nhật kèm theo tối thiểu của chúng ta. Hơn nữa, chúng tôi có thể kiểm tra từng đốm màu bằng cách sử dụng một cái gì đó như humoments nếu hoàn toàn cần thiết.

Dưới đây là những gì tôi đã đề xuất trong hành động, các kết quả trùng khớp ranh giới được hiển thị bằng màu đỏ.

enter image description here

Code:

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() 

Tôi nghĩ rằng WatersheddistanceTransform phương pháp chứng minh bởi @mmgp rõ ràng là vượt trội cho phân đoạn hình ảnh, nhưng cách tiếp cận đơn giản này có thể hiệu quả tùy thuộc vào nhu cầu của bạn.

+0

Cảm ơn bạn rất nhiều, tôi nghĩ rằng phương pháp này là nhanh hơn sau đó phương pháp đầu nguồn, bạn có thể đưa ra một số bình luận về mã? giống như "ndimage.label" có nghĩa là gì? cũng "np.array (np.where (nhãn im_label ==) [:: - 1]). T.reshape (-1,1,2) .copy()"? Cảm ơn bạn cho aswer ~ – Yang

+0

Ngoài ra "bất kỳ ([np.any (dòng [:, 0] <= 0), np.any (dòng [:, 0]> = im.shape [1] -1), np. bất kỳ (dòng [:, 1] <= 0), np.any (dòng [:, 1]> = im.shape [0] -1)]) "Tôi không thể hiểu dễ dàng. Cảm ơn! – Yang

+1

@Yang - hey, chắc chắn heres một shot: 'ndimage.label (im)' được sử dụng để phân đoạn các hình ảnh: tất cả các giá trị blobs không liên kết được thay thế tuần tự bởi một số nguyên, dẫn đến một hình ảnh có nhãn mới 'im_label'. 'np.where (im_label == label)' lấy hình ảnh có nhãn mới này và trả về các chỉ số của mỗi điểm ảnh trong hình ảnh đó bằng nhãn - tức là. tất cả các giá trị chỉ mục cho một đốm màu - lưu ý rằng chúng tôi đang lặp qua các đốm màu, chỉ xem xét một giá trị nhãn tại một thời điểm. '.T.reshape (-1,1,2) .copy()' là một câu đố để đưa dữ liệu vào đúng định dạng được chấp nhận bởi 'minAreaRect' – fraxel

Các vấn đề liên quan