二进制映像中磁盘的半径

时间:2018-11-29 11:04:12

标签: opencv image-processing

我已经将这样的图像二值化了:

enter image description here

我需要确定内部固态磁盘的中心和半径。如您所见,它被触摸的纹理区域包围,因此无法简单地检测连接的组件。无论如何,周边的大部分区域都有空白。

可能的解决方法是腐蚀直到所有纹理消失或从磁盘断开,但这可能很耗时,并且不确定迭代次数。 (此外,在某些不幸的情况下,磁盘上会出现细小的孔,随着腐蚀的加剧,孔会逐渐增大。)

有更好的建议以健壮和快速的方式解决此问题吗? (我标记了OpenCV,但这不是强制性的,重要的是方法。)

6 个答案:

答案 0 :(得分:6)

您可以:

  1. 反转图像
  2. 找到largest axis-aligned rectangle containing only zeros,(我使用了this answer中的C ++代码)。该算法非常快。
  3. 从矩形获取圆的中心和半径

enter image description here

代码:

#include <opencv2\opencv.hpp>

using namespace std;
using namespace cv;

// https://stackoverflow.com/a/30418912/5008845
cv::Rect findMaxRect(const cv::Mat1b& src)
{
    cv::Mat1f W(src.rows, src.cols, float(0));
    cv::Mat1f H(src.rows, src.cols, float(0));

    cv::Rect maxRect(0,0,0,0);
    float maxArea = 0.f;

    for (int r = 0; r < src.rows; ++r)
    {
        for (int c = 0; c < src.cols; ++c)
        {
            if (src(r, c) == 0)
            {
                H(r, c) = 1.f + ((r>0) ? H(r-1, c) : 0);
                W(r, c) = 1.f + ((c>0) ? W(r, c-1) : 0);
            }

            float minw = W(r,c);
            for (int h = 0; h < H(r, c); ++h)
            {
                minw = std::min(minw, W(r-h, c));
                float area = (h+1) * minw;
                if (area > maxArea)
                {
                    maxArea = area;
                    maxRect = cv::Rect(cv::Point(c - minw + 1, r - h), cv::Point(c+1, r+1));
                }
            }
        }
    }

    return maxRect;
}


int main()
{
    cv::Mat1b img = cv::imread("path/to/img", cv::IMREAD_GRAYSCALE);

    // Correct image
    img = img > 127;

    cv::Rect r = findMaxRect(~img);

    cv::Point center ( std::round(r.x + r.width / 2.f), std::round(r.y + r.height / 2.f));
    int radius = std::sqrt(r.width*r.width + r.height*r.height) / 2;

    cv::Mat3b out;
    cv::cvtColor(img, out, cv::COLOR_GRAY2BGR);
    cv::rectangle(out, r, cv::Scalar(0, 255, 0));
    cv::circle(out, center, radius, cv::Scalar(0, 0, 255));

    return 0;
}

答案 1 :(得分:2)

我的方法是使用morph-open,findcontours和minEnclosingCircle,如下所示:

enter image description here

#!/usr/bin/python3
# 2018/11/29 20:03 
import cv2

fname = "test.png"
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

th, threshed = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY)

kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
morphed = cv2.morphologyEx(threshed, cv2.MORPH_OPEN, kernel, iterations = 3)

cnts = cv2.findContours(morphed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]

cnt = max(cnts, key=cv2.contourArea)
pt, r = cv2.minEnclosingCircle(cnt)

pt = (int(pt[0]), int(pt[1]))
r = int(r)

print("center: {}\nradius: {}".format(pt, r))

最终结果:

enter image description here

center: (184, 170)
radius: 103

答案 2 :(得分:1)

这里是使用霍夫圆的一个例子。如果将最小半径和最大半径设置在适当的范围内,则可以正常工作。

import cv2
import numpy as np

# load image in grayscale
image = cv2.imread('radius.png',0)
r , c = image.shape

# remove noise
dst = cv2.blur(image,(5,5))

# Morphological closing
dst = cv2.erode(dst,None,iterations = 3)
dst = cv2.dilate(dst,None,iterations = 3)

# Find Hough Circle
circles = cv2.HoughCircles(dst
    ,cv2.HOUGH_GRADIENT
    ,2
    ,minDist = 0.5* r
    ,param2 = 150
    ,minRadius = int(0.5 * r / 2.0) 
    ,maxRadius = int(0.75 * r / 2.0)
    )

# Display
edges_color = cv2.cvtColor(image,cv2.COLOR_GRAY2BGR)
for i in circles[0]:
    print(i)
    cv2.circle(edges_color,(i[0],i[1]),i[2],(0,0,255),1)



cv2.imshow("edges_color",edges_color)
cv2.waitKey(0)

这是结果 [185。 167. 103.6]

enter image description here

答案 3 :(得分:1)

我第二次尝试这种情况。这次我使用形态学闭合操作来减弱噪声并保持信号。接下来是简单的阈值和关联组件分析。我希望这段代码可以运行得更快。

enter image description here

使用这种方法,我可以找到具有亚像素精度的质心

('center : ', (184.12244328746746, 170.59771290442544))

半径是从圆的面积得出的。

('radius : ', 101.34704439389715)

这是完整的代码

import cv2
import numpy as np

# load image in grayscale
image = cv2.imread('radius.png',0)
r,c = image.shape
# remove noise
blured = cv2.blur(image,(5,5))

# Morphological closing
morph = cv2.erode(blured,None,iterations = 3)
morph = cv2.dilate(morph,None,iterations = 3)
cv2.imshow("morph",morph)
cv2.waitKey(0)

# Get the strong signal
th, th_img = cv2.threshold(morph,200,255,cv2.THRESH_BINARY)

cv2.imshow("th_img",th_img)
cv2.waitKey(0)

# Get connected components
num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(th_img)
print(num_labels)
print(stats)

# displat labels
labels_disp = np.uint8(255*labels/np.max(labels))
cv2.imshow("labels",labels_disp)
cv2.waitKey(0)

# Find center label
cnt_label = labels[r/2,c/2]

# Find circle center and radius
# Radius calculated by averaging the height and width of bounding box
area = stats[cnt_label][4]
radius = np.sqrt(area / np.pi)#stats[cnt_label][2]/2 + stats[cnt_label][3]/2)/2
cnt_pt = ((centroids[cnt_label][0]),(centroids[cnt_label][1]))
print('center : ',cnt_pt)
print('radius : ',radius)

# Display final result
edges_color = cv2.cvtColor(image,cv2.COLOR_GRAY2BGR)
cv2.circle(edges_color,(int(cnt_pt[0]),int(cnt_pt[1])),int(radius),(0,0,255),1)
cv2.circle(edges_color,(int(cnt_pt[0]),int(cnt_pt[1])),5,(0,0,255),-1)

x1 = stats[cnt_label][0]
y1 = stats[cnt_label][1]
w1 = stats[cnt_label][2]
h1 = stats[cnt_label][3]
cv2.rectangle(edges_color,(x1,y1),(x1+w1,y1+h1),(0,255,0))

cv2.imshow("edges_color",edges_color)
cv2.waitKey(0)

答案 4 :(得分:0)

您是否尝试过类似Circle Hough Transform的方法? 我看到OpenCv有its own implementation。不过,这里可能需要进行一些预处理(中值过滤?)。

答案 5 :(得分:0)

这是一种简单的方法:

  1. 侵蚀图像(使用大的圆形SE),然后找到结果的质心。这应该确实接近中央磁盘的质心。
  2. 以计算的质心为中心,计算平均值作为原始图像半径的函数。

输出看起来像这样:

Radial mean of the picture in the question

从这里开始,确定半径非常简单。

这是代码,我正在使用PyDIP(我们还没有二进制发行版,您需要下载和构建表单源):

import matplotlib.pyplot as pp
import PyDIP as dip
import numpy as np

img = dip.Image(pp.imread('/home/cris/tmp/FDvQm.png')[:,:,0])
b = dip.Erosion(img, 30)
c = dip.CenterOfMass(b)
rmean = dip.RadialMean(img, center=c)
pp.plot(rmean)
r = np.argmax(rmean < 0.5)

在这里,r为102,因为以整数像素为单位的半径,我敢肯定可以进行插值以提高精度。 c[184.02, 170.45]