Обнаружить несколько прямоугольников на изображении

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 и получил более близкие результаты, но все еще упускал несколько очевидных прямоугольников.

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

Donny
источник
на вашем изображении сумасшедшего, сделайте расширенную операцию. Затем определите только внутренние контуры (первый уровень).
Мика
Можете ли вы предоставить изображение маски в виде PNG?
Мика
1
Я обновил вопрос с версией png
Донни
Есть ли у вас правдивая правда о том, сколько труб должно быть обнаружено?
TA
Одной из вещей, которую вы можете попробовать, может быть настройка шага порогового уровня для улучшения пропущенных обнаружений. Посмотрите на пороговое или адаптивное пороговое значение Оцу. Тем не менее, ваше текущее решение, вероятно, лучшее, что вы получите, используя традиционные методы обработки изображений. В противном случае вы можете заглянуть в глубокое / машинное обучение
Натанси

Ответы:

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);
MH304
источник
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-подобной обработки введите описание изображения здесь

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

Окончательный результат с красными прямоугольниками, показывающими все кандидаты в BLOB, и желтыми сегментами, показывающими BLOB-объекты, которые отвечают всем установленным нами условиям. Ниже и сверху трубного пучка есть несколько ложных тревог, но они могут быть опущены при некоторых граничных условиях. введите описание изображения здесь

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

yapws87
источник
Я получаю эту ошибку при выполнении кода blob, _ = cv2.findContours (matblobs, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) ValueError: недостаточно значений для распаковки (ожидается 3, получено 2)
Донни
вы должны использовать другую версию opencv. Все, что вам нужно сделать, это удалить первое подчеркивание, "_", из исходного кода для получения из функции. blob, _ = cv2.findContours (matblobs, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
yapws87