Виявити декілька прямокутників на зображенні


13

Я намагаюся виявити кількість труб на цій фотографії. Для цього я використовую виявлення на основі OpenCV та Python. На підставі існуючих відповідей на подібні запитання я зміг придумати наступні кроки

  1. Відкрийте зображення
  2. Профільтруйте його
  3. Застосувати виявлення країв
  4. Використовуйте контури
  5. Перевірте кількість

введіть тут опис зображення

Загальна кількість труб становить ~ 909, коли ми підраховуємо її вручну даємо або беремо 4.

Після застосування фільтра

import cv2
import matplotlib.pyplot as plt
import numpy as np

img = cv2.imread('images/input-rectpipe-1.jpg')
blur_hor = cv2.filter2D(img[:, :, 0], cv2.CV_32F, kernel=np.ones((11,1,1), np.float32)/11.0, borderType=cv2.BORDER_CONSTANT)
blur_vert = cv2.filter2D(img[:, :, 0], cv2.CV_32F, kernel=np.ones((1,11,1), np.float32)/11.0, borderType=cv2.BORDER_CONSTANT)
mask = ((img[:,:,0]>blur_hor*1.2) | (img[:,:,0]>blur_vert*1.2)).astype(np.uint8)*255

Я отримую це масковане зображення

введіть тут опис зображення

Це виглядає досить точно з точки зору кількості видимих ​​прямокутників, які він показує. Однак, коли я намагаюся взяти підрахунок і побудувати обмежувальну рамку вгорі картини, вона також вибирає безліч небажаних регіонів. Для кругів HoughCircles має спосіб визначення максимального та мінімального радіусу. Чи є щось подібне для прямокутників, що може підвищити точність. Також я відкритий для пропозицій щодо альтернативних підходів до цієї проблеми.

ret,thresh = cv2.threshold(mask,127,255,0)
contours,hierarchy = cv2.findContours(thresh, 1, 2)

count = 0

for i in range(len(contours)):

  count = count+1
  x,y,w,h = cv2.boundingRect(contours[i]) 
  rect = cv2.minAreaRect(contours[i])
  area = cv2.contourArea(contours[i])
  box = cv2.boxPoints(rect)
  ratio = w/h
  M = cv2.moments(contours[i])

  if M["m00"] == 0.0:
         cX = int(M["m10"] / 1 )
         cY = int(M["m01"] / 1 )

  if M["m00"] != 0.0:
    cX = int(M["m10"] / M["m00"])
    cY = int(M["m01"] / M["m00"])

  if (area > 50 and area < 220 and hierarchy[0][i][2] < 0 and (ratio > .5 and ratio < 2)):
    #cv2.rectangle(img, (x,y), (x+w,y+h), (0,255,0), 2)
    cv2.circle(img, (cX, cY), 1, (255, 255, 255), -1)
    count = count + 1 



print(count)

cv2.imshow("m",mask)
cv2.imshow("f",img)
cv2.waitKey(0)

введіть тут опис зображення

ОНОВЛЕННЯ На основі другої відповіді я перетворив код c ++ в код python і отримав більш близькі результати, але все ще пропускаю декілька очевидних прямокутників.

введіть тут опис зображення


на вашому божевільному зображенні виконайте операцію з розширення. Тоді виявляйте лише внутрішні контури (перший рівень).
Міцка

чи можете ви надати зображення своєї маски як png?
Міцка

1
Я оновив питання з версією png
Донні

Чи маєте ви основну правду про те, скільки труб слід виявити?
ТА

Одне, що ви можете спробувати, - це налаштувати пороговий крок, щоб покращити виявлення відсутніх. Подивіться на поріг Otsu або адаптивний поріг. Однак ваше поточне рішення, мабуть, найкраще, що ви отримаєте, використовуючи традиційні методи обробки зображень. В іншому випадку ви можете заглянути в глибоке / машинне навчання
Натансі

Відповіді:


6

Звичайно, ви можете відфільтрувати їх за їхньою областю. Я взяв ваше двійкове зображення і продовжив роботу, як показано нижче:

1- Зробіть петлю по всіх контурах, які ви знайшли з findContours

2- У циклі перевірте, чи є кожен контур, це внутрішній контур чи ні

3- З тих, що є внутрішніми контурами, перевірте їх площу, і якщо площа знаходиться у прийнятному діапазоні, перевірте співвідношення ширини / висоти кожного контуру і, нарешті, якщо воно теж добре, порахуйте цей контур як трубу.

Я зробив описаний вище метод на вашому бінарному зображенні та виявив 794 труби :

введіть тут опис зображення

(Однак деякі коробки втрачені. Вам слід змінити параметри детектора ребер, щоб отримати більше відокремлених коробок на зображенні.)

і ось код (Це c ++, але легко конвертований у python):

Mat img__1, img__2,img__ = imread("E:/R.jpg", 0);

threshold(img__, img__1, 128, 255, THRESH_BINARY);

vector<vector<Point>> contours;
vector< Vec4i > hierarchy;

findContours(img__1, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_NONE);

Mat tmp = Mat::zeros(img__1.size(), CV_8U);
int k = 0;
for (size_t i = 0; i < contours.size(); i++)
{
    double area = contourArea(contours[i]);
    Rect rec = boundingRect(contours[i]);
    float ratio = rec.width / float(rec.height);

    if (area > 50 && area < 220 && hierarchy[i][2]<0 && (ratio > .5 && ratio < 2) ) # hierarchy[i][2]<0 stands for internal contours
    {
        k++;
        drawContours(tmp, contours, i, Scalar(255, 255, 255), -1);
    }
}
cout << "k= " << k << "\n";
imshow("1", img__1); 
imshow("2", tmp);
waitKey(0);

2

Існує багато методів вирішення цієї проблеми, але я сумніваюся, що буде єдиний метод без якихось заходів. Ось ще одна спроба цієї проблеми.

Замість використання крайової інформації я пропоную фільтр, подібний LBP (локальний бінарний візерунок), який порівнює навколишній піксель із центральним значенням. Якщо певний відсоток оточуючого пікселя більший, ніж центральний піксель, центральний піксель буде позначений 255. Якщо умова не виконується, то центральний піксель буде позначений 0.

Цей метод, заснований на інтенсивності, працює за припущенням, що центр труби завжди темніше, ніж краї труби. Оскільки він порівнює інтенсивність, він повинен працювати добре, поки залишається деякий контраст.

Завдяки цьому процесу ви отримаєте зображення з бінарними краплями для кожної труби та деякими шумами. Вам доведеться видалити їх із деякими попередньо відомими умовами, такими як, розмір, форма, fill_ratio, колір тощо. Умова можна знайти у вказаному коді.

import cv2
import matplotlib.pyplot as plt
import numpy as np

# Morphological function sets
def morph_operation(matinput):
  kernel =  cv2.getStructuringElement(cv2.MORPH_CROSS,(3,3))

  morph = cv2.erode(matinput,kernel,iterations=1)
  morph = cv2.dilate(morph,kernel,iterations=2)
  morph = cv2.erode(matinput,kernel,iterations=1)
  morph = cv2.dilate(morph,kernel,iterations=1)

  return morph


# Analyze blobs
def analyze_blob(matblobs,display_frame):

  _,blobs,_ = cv2.findContours(matblobs,cv2.RETR_LIST ,cv2.CHAIN_APPROX_SIMPLE)
  valid_blobs = []

  for i,blob in enumerate(blobs):
    rot_rect = cv2.minAreaRect(blob)
    b_rect = cv2.boundingRect(blob)


    (cx,cy),(sw,sh),angle = rot_rect
    rx,ry,rw,rh = b_rect

    box = cv2.boxPoints(rot_rect)
    box = np.int0(box)

    # Draw the segmented Box region
    frame = cv2.drawContours(display_frame,[box],0,(0,0,255),1)

    on_count = cv2.contourArea(blob)
    total_count = sw*sh
    if total_count <= 0:
      continue

    if sh > sw :
      temp = sw
      sw = sh
      sh = temp

    # minimum area
    if sw * sh < 20:
      continue

    # maximum area
    if sw * sh > 100:
      continue  

    # ratio of box
    rect_ratio = sw / sh
    if rect_ratio <= 1 or rect_ratio >= 3.5:
      continue

    # ratio of fill  
    fill_ratio = on_count / total_count
    if fill_ratio < 0.4 :
      continue

    # remove blob that is too bright
    if display_frame[int(cy),int(cx),0] > 75:
      continue


    valid_blobs.append(blob)

  if valid_blobs:
    print("Number of Blobs : " ,len(valid_blobs))
  cv2.imshow("display_frame_in",display_frame)

  return valid_blobs

def lbp_like_method(matinput,radius,stren,off):

  height, width = np.shape(matinput)

  roi_radius = radius
  peri = roi_radius * 8
  matdst = np.zeros_like(matinput)
  for y in range(height):
    y_ = y - roi_radius
    _y = y + roi_radius
    if y_ < 0 or _y >= height:
      continue


    for x in range(width):
      x_ = x - roi_radius
      _x = x + roi_radius
      if x_ < 0 or _x >= width:
        continue

      r1 = matinput[y_:_y,x_]
      r2 = matinput[y_:_y,_x]
      r3 = matinput[y_,x_:_x]
      r4 = matinput[_y,x_:_x]

      center = matinput[y,x]
      valid_cell_1 = len(r1[r1 > center + off])
      valid_cell_2 = len(r2[r2 > center + off])
      valid_cell_3 = len(r3[r3 > center + off])
      valid_cell_4 = len(r4[r4 > center + off])

      total = valid_cell_1 + valid_cell_2 + valid_cell_3 + valid_cell_4

      if total > stren * peri:
        matdst[y,x] = 255

  return matdst


def main_process():

  img = cv2.imread('image.jpg')    
  gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)



  # Blured to remove noise 
  blurred = cv2.GaussianBlur(gray,(3,3),-1)

  # Parameter tuning
  winsize = 5
  peri = 0.6
  off = 4

  matlbp = lbp_like_method(gray,winsize,peri,off)
  cv2.imshow("matlbp",matlbp)
  cv2.waitKey(1)

  matmorph = morph_operation(matlbp)
  cv2.imshow("matmorph",matmorph)
  cv2.waitKey(1)


  display_color = cv2.cvtColor(gray,cv2.COLOR_GRAY2BGR)
  valid_blobs = analyze_blob(matmorph,display_color)


  for b in range(len(valid_blobs)):
    cv2.drawContours(display_color,valid_blobs,b,(0,255,255),-1)


  cv2.imshow("display_color",display_color)
  cv2.waitKey(0)


if __name__ == '__main__':
  main_process()

Результат від LBP-подібної обробки введіть тут опис зображення

Після очищення морфологічним процесом введіть тут опис зображення

Кінцевий результат: червоні поля, що показують усіх кандидатів на блоб, і жовті сегменти, що показують краплі, які відповідають усім умовам, які ми встановили. Унизу та вгорі пакета труби є кілька помилкових сигналів тривоги, але їх можна опустити за певних граничних умов. введіть тут опис зображення

Всього знайдено труб: 943


Я отримую цю помилку під час запуску коду, blobs, _ = cv2.findContours (matblobs, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) ValueError: недостатньо значень для розпакування (очікується 3, отримано 2)
Donny

ви повинні використовувати іншу версію opencv. Все, що вам потрібно зробити, - це видалити перше підкреслення "_" з початкового коду, який отримаєте від функції. краплі, _ = cv2.findContours (matblobs, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
yapws87
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.