opencv-058-二值图像分析(寻找最大内接圆)

知识点

对于轮廓来说,有时候我们会需要选择最大内接圆,OpenCV中没有现成的API可以使用,但是我们可以通过点多边形测试巧妙的获取轮廓最大内接圆的半径,从点多边形测试的返回结果我们知道,它返回的是像素距离,而且是当前点距离轮廓最近的距离,当这个点在轮廓内部,其返回的距离是最大值的时候,其实这个距离就是轮廓的最大内接圆的半径,这样我们就巧妙的获得了圆心的位置与半径,然后绘制。

代码(c++,python)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

/*
* 二值图像分析(寻找最大内接圆)
*/
int main() {
const int r = 100;
Mat src = Mat::zeros(Size(4 * r, 4 * r), CV_8U);
vector<Point2f> vert(6);
vert[0] = Point(3 * r / 2, static_cast<int>(1.34 * r));
vert[1] = Point(1 * r, 2 * r);
vert[2] = Point(3 * r / 2, static_cast<int>(2.866 * r));
vert[3] = Point(5 * r / 2, static_cast<int>(2.866 * r));
vert[4] = Point(3 * r, 2 * r);
vert[5] = Point(5 * r / 2, static_cast<int>(1.34 * r));
for (int i = 0; i < 6; ++i) {
line(src, vert[i], vert[(i + 1) % 6], Scalar(255), 3);
}
imshow("input", src);

// 点多边形测试
vector<vector<Point> > contours;
findContours(src, contours, RETR_TREE, CHAIN_APPROX_SIMPLE);
Mat raw_dist(src.size(), CV_32F);
for (int i = 0; i < src.rows; ++i) {
for (int j = 0; j < src.cols; ++j) {
raw_dist.at<float>(i, j) = (float) pointPolygonTest(contours[0],
Point2f((float) j, (float) i), true);
}
}

// 获取最大内接圆半径
double minval, maxval;
Point maxDistPt;// save circle center
minMaxLoc(raw_dist, &minval, &maxval, NULL, &maxDistPt);
minval = abs(minval);
maxval = abs(maxval);

Mat drawing = Mat::zeros(src.size(), CV_8UC3);
for (int i = 0; i < src.rows; ++i) {
for (int j = 0; j < src.cols; ++j) {
if (raw_dist.at<float>(i, j) < 0) {
drawing.at<Vec3b>(i, j)[0] = (uchar) (255 - abs(raw_dist.at<float>(i, j)) * 255 / minval);
} else if (raw_dist.at<float>(i, j) > 0) {
drawing.at<Vec3b>(i, j)[2] = (uchar) (255 - raw_dist.at<float>(i, j) * 255 / maxval);
} else {
drawing.at<Vec3b>(i, j)[0] = 255;
drawing.at<Vec3b>(i, j)[1] = 255;
drawing.at<Vec3b>(i, j)[2] = 255;
}
}
}

// 绘制内接圆
circle(drawing, maxDistPt, (int)maxval, Scalar(255,255,255));
imshow("distance_inscribed_circle", drawing);

waitKey(0);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
from __future__ import print_function
from __future__ import division
import cv2 as cv
import numpy as np
# Create an image
r = 100
src = np.zeros((4*r, 4*r), dtype=np.uint8)
# Create a sequence of points to make a contour
vert = [None]*6
vert[0] = (3*r//2, int(1.34*r))
vert[1] = (1*r, 2*r)
vert[2] = (3*r//2, int(2.866*r))
vert[3] = (5*r//2, int(2.866*r))
vert[4] = (3*r, 2*r)
vert[5] = (5*r//2, int(1.34*r))
# Draw it in src
for i in range(6):
cv.line(src, vert[i], vert[(i+1)%6], ( 255 ), 3)
# Get the contours
_, contours, _ = cv.findContours(src, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)

# Calculate the distances to the contour
raw_dist = np.empty(src.shape, dtype=np.float32)
for i in range(src.shape[0]):
for j in range(src.shape[1]):
raw_dist[i,j] = cv.pointPolygonTest(contours[0], (j,i), True)

# 获取最大值即内接圆半径,中心点坐标
minVal, maxVal, _, maxDistPt = cv.minMaxLoc(raw_dist)
minVal = abs(minVal)
maxVal = abs(maxVal)

# Depicting the distances graphically
drawing = np.zeros((src.shape[0], src.shape[1], 3), dtype=np.uint8)
for i in range(src.shape[0]):
for j in range(src.shape[1]):
if raw_dist[i,j] < 0:
drawing[i,j,0] = 255 - abs(raw_dist[i,j]) * 255 / minVal
elif raw_dist[i,j] > 0:
drawing[i,j,2] = 255 - raw_dist[i,j] * 255 / maxVal
else:
drawing[i,j,0] = 255
drawing[i,j,1] = 255
drawing[i,j,2] = 255

# max inner circle
cv.circle(drawing,maxDistPt, np.int(maxVal),(255,255,255), 1, cv.LINE_8, 0)
cv.imshow('Source', src)
cv.imshow('Distance and inscribed circle', drawing)

cv.waitKey(0)
cv.destroyAllWindows()

结果

代码地址

github