【问题标题】:Detect touching/overlapping circles/ellipses with OpenCV and Python使用 OpenCV 和 Python 检测接触/重叠的圆/椭圆
【发布时间】:2015-01-12 00:22:32
【问题描述】:

我想测量圆的圆度(“圆”高度和宽度或椭圆参数的差异)。圆圈在图片中给出,如下所示:

在做了color2gray、阈值和边界检测等常规操作后,我得到如下图所示:

有了这个,我已经尝试了很多不同的东西:

  • List item Watershed with findContour(类似于this question)-> openCV 将圆圈之间的空间检测为闭合轮廓而不是圆圈,因为它们粘在一起不形成闭合轮廓
  • fitEllipse 存在同样的问题。我将椭圆拟合在黑色背景轮廓上,而不是介于两者之间。
  • 只是尝试应用霍夫变换(如代码和第三张图片所示)也会导致奇怪的结果:

在此处查看代码:

import sys
import cv2
import numpy
from scipy.ndimage import label

# Application entry point
#img = cv2.imread("02_adj_grey.jpg")
img = cv2.imread("fuss02.jpg")

# Pre-processing.
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)    
cv2.imwrite("SO_0_gray.png", img_gray)

#_, img_bin = cv2.threshold(img_gray, 0, 255, cv2.THRESH_OTSU | cv2.THRESH_BINARY)
_, img_bin = cv2.threshold(img_gray, 170, 255, cv2.THRESH_BINARY)
cv2.imwrite("SO_1_threshold.png", img_bin)

#blur = cv2.GaussianBlur(img,(5,5),0)
img_bin = cv2.morphologyEx(img_bin, cv2.MORPH_CLOSE, numpy.ones((3, 3), dtype=int))
cv2.imwrite("SO_2_img_bin_morphoEx.png", img_bin)

border = img_bin - cv2.erode(img_bin, None)
cv2.imwrite("SO_3_border.png", border)


circles = cv2.HoughCircles(border,cv2.cv.CV_HOUGH_GRADIENT,50,80, param1=80,param2=40,minRadius=10,maxRadius=150)
print circles

cimg = img
for i in circles[0,:]:
# draw the outer circle
    cv2.circle(cimg,(i[0],i[1]),i[2],(0,255,0),2)
    # draw the center of the circle
    cv2.circle(cimg,(i[0],i[1]),2,(0,0,255),3)
    cv2.putText(cimg,str(i[0])+str(',')+str(i[1]), (i[0],i[1]), cv2.FONT_HERSHEY_SIMPLEX, 0.4, 255)

cv2.imwrite("SO_8_cimg.png", cimg)

有没有人想改进我的算法或完全不同的方法?我一直在尝试许多不同的方法,但到目前为止没有运气。感谢大家的帮助。

【问题讨论】:

  • 您在从图像中提取圆圈时遇到问题吗?我不太符合你的要求。
  • 是的,正如您在上面的边界检测图像中看到的那样,我无法分离圆圈。在过滤等过程中会丢失很多边框。
  • 我有一些想法。给我一点儿

标签: python opencv image-processing feature-detection hough-transform


【解决方案1】:

我的代码有一些错误@dhanuskha。我想是因为我使用的是不同版本的 CV。如果您需要,此代码适用于 CV 3.0。

import cv2

im = cv2.imread('input.png')
hsv = cv2.cvtColor(im, cv2.COLOR_BGR2HSV)
th, bw = cv2.threshold(hsv[:, :, 2], 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
morph = cv2.morphologyEx(bw, cv2.MORPH_CLOSE, kernel)
dist = cv2.distanceTransform(morph, cv2.DIST_L2, cv2.DIST_MASK_PRECISE)
borderSize = 75
distborder = cv2.copyMakeBorder(dist, borderSize, borderSize, borderSize, borderSize, 
                                cv2.BORDER_CONSTANT | cv2.BORDER_ISOLATED, 0)
gap = 10                                
kernel2 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (2*(borderSize-gap)+1, 2*(borderSize-gap)+1))
kernel2 = cv2.copyMakeBorder(kernel2, gap, gap, gap, gap, 
                                cv2.BORDER_CONSTANT | cv2.BORDER_ISOLATED, 0)
distTempl = cv2.distanceTransform(kernel2, cv2.DIST_L2, cv2.DIST_MASK_PRECISE)
nxcor = cv2.matchTemplate(distborder, distTempl, cv2.TM_CCOEFF_NORMED)
mn, mx, _, _ = cv2.minMaxLoc(nxcor)
th, peaks = cv2.threshold(nxcor, mx*0.5, 255, cv2.THRESH_BINARY)
peaks8u = cv2.convertScaleAbs(peaks)
_, contours, hierarchy = cv2.findContours(peaks8u, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
peaks8u = cv2.convertScaleAbs(peaks)    # to use as mask
for i in range(len(contours)):
    x, y, w, h = cv2.boundingRect(contours[i])
    _, mx, _, mxloc = cv2.minMaxLoc(dist[y:y+h, x:x+w], peaks8u[y:y+h, x:x+w])
    cv2.circle(im, (int(mxloc[0]+x), int(mxloc[1]+y)), int(mx), (255, 0, 0), 2)
    cv2.rectangle(im, (x, y), (x+w, y+h), (0, 255, 255), 2)
    cv2.drawContours(im, contours, i, (0, 0, 255), 2)

cv2.imshow('circles', im)
cv2.waitKey(0)

【讨论】:

    【解决方案2】:

    这是我检测圆圈的尝试。总结

    • 执行 BGR->HSV 转换并使用 V 通道进行处理

    V 频道:

    • 阈值,应用形态闭合,然后进行距离变换(我称之为dist

    dist 图片:

    • 创建一个模板。从图像中圆圈的大小来看,半径约为 75 像素的圆盘看起来是合理的。把它的距离变换作为模板(我称之为temp

    温度图片:

    • 执行模板匹配:dist * temp

    dist * temp 图片:

    • 找到结果图像的局部最大值。最大值的位置对应于圆心,最大值对应于它们的半径

    阈值模板匹配图像:

    将圆检测为局部最大值:

    我是用 C++ 做的,因为我觉得它最舒服。如果你觉得这很有用,我认为你可以轻松地将其转换为 python。请注意,以上图片未按比例绘制。希望这会有所帮助。

    编辑:添加了 Python 版本

    C++:

        double min, max;
        Point maxLoc;
    
        Mat im = imread("04Bxy.jpg");
        Mat hsv;
        Mat channels[3];
        // bgr -> hsv
        cvtColor(im, hsv, CV_BGR2HSV);
        split(hsv, channels);
        // use v channel for processing
        Mat& ch = channels[2];
        // apply Otsu thresholding
        Mat bw;
        threshold(ch, bw, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
        // close small gaps
        Mat kernel = getStructuringElement(MORPH_ELLIPSE, Size(3, 3));
        Mat morph;
        morphologyEx(bw, morph, CV_MOP_CLOSE, kernel);
        // take distance transform
        Mat dist;
        distanceTransform(morph, dist, CV_DIST_L2, CV_DIST_MASK_PRECISE);
        // add a black border to distance transformed image. we are going to do
        // template matching. to get a good match for circles in the margin, we are adding a border
        int borderSize = 75;
        Mat distborder(dist.rows + 2*borderSize, dist.cols + 2*borderSize, dist.depth());
        copyMakeBorder(dist, distborder, 
                borderSize, borderSize, borderSize, borderSize, 
                BORDER_CONSTANT | BORDER_ISOLATED, Scalar(0, 0, 0));
        // create a template. from the sizes of the circles in the image, 
        // a ~75 radius disk looks reasonable, so the borderSize was selected as 75
        Mat distTempl;
        Mat kernel2 = getStructuringElement(MORPH_ELLIPSE, Size(2*borderSize+1, 2*borderSize+1));
        // erode the ~75 radius disk a bit
        erode(kernel2, kernel2, kernel, Point(-1, -1), 10);
        // take its distance transform. this is the template
        distanceTransform(kernel2, distTempl, CV_DIST_L2, CV_DIST_MASK_PRECISE);
        // match template
        Mat nxcor;
        matchTemplate(distborder, distTempl, nxcor, CV_TM_CCOEFF_NORMED);
        minMaxLoc(nxcor, &min, &max);
        // threshold the resulting image. we should be able to get peak regions.
        // we'll locate the peak of each of these peak regions
        Mat peaks, peaks8u;
        threshold(nxcor, peaks, max*.5, 255, CV_THRESH_BINARY);
        convertScaleAbs(peaks, peaks8u);
        // find connected components. we'll use each component as a mask for distance transformed image,
        // then extract the peak location and its strength. strength corresponds to the radius of the circle
        vector<vector<Point>> contours;
        vector<Vec4i> hierarchy;
        findContours(peaks8u, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
        for(int idx = 0; idx >= 0; idx = hierarchy[idx][0])
        {
            // prepare the mask
            peaks8u.setTo(Scalar(0, 0, 0));
            drawContours(peaks8u, contours, idx, Scalar(255, 255, 255), -1);
            // find the max value and its location in distance transformed image using mask
            minMaxLoc(dist, NULL, &max, NULL, &maxLoc, peaks8u);
            // draw the circles
            circle(im, maxLoc, (int)max, Scalar(0, 0, 255), 2);
        }
    

    Python:

    import cv2
    
    im = cv2.imread('04Bxy.jpg')
    hsv = cv2.cvtColor(im, cv2.COLOR_BGR2HSV)
    th, bw = cv2.threshold(hsv[:, :, 2], 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
    morph = cv2.morphologyEx(bw, cv2.MORPH_CLOSE, kernel)
    dist = cv2.distanceTransform(morph, cv2.cv.CV_DIST_L2, cv2.cv.CV_DIST_MASK_PRECISE)
    borderSize = 75
    distborder = cv2.copyMakeBorder(dist, borderSize, borderSize, borderSize, borderSize, 
                                    cv2.BORDER_CONSTANT | cv2.BORDER_ISOLATED, 0)
    gap = 10                                
    kernel2 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (2*(borderSize-gap)+1, 2*(borderSize-gap)+1))
    kernel2 = cv2.copyMakeBorder(kernel2, gap, gap, gap, gap, 
                                    cv2.BORDER_CONSTANT | cv2.BORDER_ISOLATED, 0)
    distTempl = cv2.distanceTransform(kernel2, cv2.cv.CV_DIST_L2, cv2.cv.CV_DIST_MASK_PRECISE)
    nxcor = cv2.matchTemplate(distborder, distTempl, cv2.TM_CCOEFF_NORMED)
    mn, mx, _, _ = cv2.minMaxLoc(nxcor)
    th, peaks = cv2.threshold(nxcor, mx*0.5, 255, cv2.THRESH_BINARY)
    peaks8u = cv2.convertScaleAbs(peaks)
    contours, hierarchy = cv2.findContours(peaks8u, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
    peaks8u = cv2.convertScaleAbs(peaks)    # to use as mask
    for i in range(len(contours)):
        x, y, w, h = cv2.boundingRect(contours[i])
        _, mx, _, mxloc = cv2.minMaxLoc(dist[y:y+h, x:x+w], peaks8u[y:y+h, x:x+w])
        cv2.circle(im, (int(mxloc[0]+x), int(mxloc[1]+y)), int(mx), (255, 0, 0), 2)
        cv2.rectangle(im, (x, y), (x+w, y+h), (0, 255, 255), 2)
        cv2.drawContours(im, contours, i, (0, 0, 255), 2)
    
    cv2.imshow('circles', im)
    

    【讨论】:

    猜你喜欢
    • 2017-07-01
    • 2012-06-14
    • 1970-01-01
    • 2015-08-06
    • 2021-10-10
    • 1970-01-01
    • 2014-03-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多