检测图像中的多个矩形


13

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

  1. 打开图片
  2. 筛选
  3. 应用边缘检测
  4. 使用轮廓
  5. 检查计数

在此处输入图片说明

当我们手动给定或取4时,管道的总数约为909

应用滤镜后

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版本更新了问题
Donny

关于检测多少个管道,您是否有基本道理?
TA

您可以尝试做的一件事是调整阈值设置步骤,以改善丢失的检测。研究大津的阈值或自适应阈值。但是,当前的解决方案可能是使用传统图像处理技术可获得的最佳解决方案。否则,您可以研究深度/机器学习
Nathancy

Answers:


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。您需要做的就是从原始代码中删除第一个下划线“ _”,以从函数中接收。blob,_ = cv2.findContours(matblobs,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
yapws87
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.