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

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

标签: python opencv image-processing computer-vision object-detection


【解决方案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)
    猜你喜欢
    • 2014-04-30
    • 2016-09-19
    • 2011-08-22
    • 1970-01-01
    • 1970-01-01
    • 2018-01-27
    • 1970-01-01
    • 2019-08-05
    • 2011-11-12
    相关资源
    最近更新 更多