检测图像中的多个矩形

Posted

技术标签:

【中文标题】检测图像中的多个矩形【英文标题】:Detect multiple rectangles in image 【发布时间】:2020-04-21 06:01:11 【问题描述】:

我正在尝试检测这张图片中的管道数。为此,我使用 OpenCV 和基于 Python 的检测。基于对类似问题的现有答案,我能够提出以下步骤

    打开图片 过滤它 应用边缘检测 使用轮廓 检查计数

当我们手动计算时,管道的总数为 ~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 吗? 我已经用 png 版本更新了问题 您对应该检测多少个管道有基本的了解吗? 您可以尝试的一件事是调整阈值步骤以改善缺失的检测。查看 Otsu 的阈值或自适应阈值。但是,您当前的解决方案可能是您使用传统图像处理技术所能获得的最佳解决方案。否则,您可以研究深度/机器学习 【参考方案1】:

当然,您可以按区域过滤它们。我拿了你的二进制图像并继续如下工作:

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。

这种基于强度的方法是在假设管道中心总是比管道边缘更暗的情况下运行的。由于它是比较强度,所以只要有一些对比度,它应该可以很好地工作。

通过这个过程,您将获得每根管道都有二进制斑点和一些噪声的图像。您必须使用一些预先知道的条件(例如大小、形状、填充比率、颜色等)来删除它们。条件可以在给定的代码中找到。

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

【讨论】:

运行代码时出现此错误,blob,_ = cv2.findContours(matblobs,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE) ValueError: no enough values to unpack (expected 3, got 2) 您必须使用不同版本的 opencv。您需要做的就是从原始代码中删除第一个下划线“_”,以便从函数中接收。斑点,_ = cv2.findContours(matblobs,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)

以上是关于检测图像中的多个矩形的主要内容,如果未能解决你的问题,请参考以下文章

检测图像中的矩形并裁剪

分组多个边界框

如何在pygame中检测两个矩形对象或图像之间的碰撞

将检测到的矩形从纵向 CIImage 转换为横向 CIImage

使用opencv将旋转和倾斜的图像插入另一个图像中检测到的矩形?

我想要在opencv中用最小矩形外接图中图像并剪切截取出这个矩形部分。