用OpenCV和Python检测触摸/重叠圆/椭圆
问题描述:
我想测量圆的圆度(“圆”的高度和宽度或椭圆参数的差异)。该圆圈中的图片给出如下所示:用OpenCV和Python检测触摸/重叠圆/椭圆
做平常的东西像color2gray,阈值和边界检测之后,我得到如下图所示:
有了这个,我已经尝试了很多不同的东西:
- 列表项与findContour的流域(类似于this question) - > openCV将圆圈之间的空间检测为闭合轮廓,而不是圆圈,因为它们粘在一起不形成闭合轮廓
- 与fitEllipse相同的问题。我在椭圆形背景上安装椭圆而不是在两者之间。
- 只是想申请霍夫transforamtion(如代码,并显示第三画面),以及导致奇怪的结果:
在这里看到的代码:
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)
有没有人有想法来改进我的算法或完全不同的方法?到目前为止,我一直在尝试许多不同的方法,但没有成功。谢谢大家的帮助。
答
这是我在检测圆圈的尝试。总之
- 执行BGR-> HSV的转换,并使用V通道进行处理
V通道:
- 阈值时,应用形态学闭,则取距离变换(我会称之为dist)
DIST图像:
- 创建模板。从图像中圆圈的大小看,一个~75像素半径的磁盘看起来是合理的。以它的距离变换,并用它作为模板(我称之为临时)
温度图像:
- 执行模板匹配:DIST * temp
DIST *临时图像:
- 找到最终图像的局部最大值。最大值的位置对应于圆心和最大值对应其半径
阈值模板匹配的图像:
检测界称为局部最大值:
我用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)
你是否对从图像中提取出界的一个问题?我不太喜欢你想要的。 – rayryeng 2014-11-14 17:25:47
是的,我无法像上面的边框检测图像中看到的那样分开圆圈。在过滤等过程中,很多边框都会丢失。 – Merlin 2014-11-15 16:37:38
我有一些想法。给我一点点 – rayryeng 2014-11-15 16:47:23