这是我的尝试。代码是用C++编写的,但由于大多数是OpenCV函数,因此可以轻松移植到Python。
方法简要概述,代码中的注释也应该有所帮助。
- 加载图像
- 转换为灰度
- 对图像二值化(阈值处理)
- 细化,获得细线轮廓并帮助
findContours
- 获取轮廓
对于每个轮廓,获取凸包(以处理开放式轮廓),并根据圆度进行分类。以不同方式处理每种形状。
- 圆形:找到最小外接圆或最佳匹配椭圆
- 矩形:查找边界框或最小定向边界框
- 三角形:搜索最小外接圆与原始形状的交点,因为它们将在三角形的三个顶点相交。
注意:
- 我需要将原始图像从具有透明度的png修改为3通道RGB。
- 细化代码来自这里。也有Python版本。
- 圆度定义为:A测量形状接近圆形的程度。例如,正六边形的圆度比正方形高。被定义为(\frac{4*\pi*Area}{perimeter * perimeter})。这意味着圆的圆度为1,正方形的圆度为0.785等等。
- 由于轮廓,每种形状可能会有多个检测结果。可以根据重叠区域条件进行过滤。我现在没有将此部分插入代码中,因为它需要附加的逻辑与找到形状的主要任务并没有直接关系。
更新-刚刚注意到在OpenCV 3.0.0中有函数minEnclosingTriangle。这可能有助于用来查找三角形的顶点而不是使用我的过程。但是,由于将此函数插入代码将是微不足道的,因此我将保留我的过程以防一个人没有OpenCV 3.0.0。
代码:
#include <opencv2\opencv.hpp>
#include <vector>
#include <iostream>
using namespace std;
using namespace cv;
void thinningIteration(cv::Mat& img, int iter)
{
CV_Assert(img.channels() == 1);
CV_Assert(img.depth() != sizeof(uchar));
CV_Assert(img.rows > 3 && img.cols > 3);
cv::Mat marker = cv::Mat::zeros(img.size(), CV_8UC1);
int nRows = img.rows;
int nCols = img.cols;
if (img.isContinuous()) {
nCols *= nRows;
nRows = 1;
}
int x, y;
uchar *pAbove;
uchar *pCurr;
uchar *pBelow;
uchar *nw, *no, *ne;
uchar *we, *me, *ea;
uchar *sw, *so, *se;
uchar *pDst;
pAbove = NULL;
pCurr = img.ptr<uchar>(0);
pBelow = img.ptr<uchar>(1);
for (y = 1; y < img.rows - 1; ++y) {
pAbove = pCurr;
pCurr = pBelow;
pBelow = img.ptr<uchar>(y + 1);
pDst = marker.ptr<uchar>(y);
no = &(pAbove[0]);
ne = &(pAbove[1]);
me = &(pCurr[0]);
ea = &(pCurr[1]);
so = &(pBelow[0]);
se = &(pBelow[1]);
for (x = 1; x < img.cols - 1; ++x) {
nw = no;
no = ne;
ne = &(pAbove[x + 1]);
we = me;
me = ea;
ea = &(pCurr[x + 1]);
sw = so;
so = se;
se = &(pBelow[x + 1]);
int A = (*no == 0 && *ne == 1) + (*ne == 0 && *ea == 1) +
(*ea == 0 && *se == 1) + (*se == 0 && *so == 1) +
(*so == 0 && *sw == 1) + (*sw == 0 && *we == 1) +
(*we == 0 && *nw == 1) + (*nw == 0 && *no == 1);
int B = *no + *ne + *ea + *se + *so + *sw + *we + *nw;
int m1 = iter == 0 ? (*no * *ea * *so) : (*no * *ea * *we);
int m2 = iter == 0 ? (*ea * *so * *we) : (*no * *so * *we);
if (A == 1 && (B >= 2 && B <= 6) && m1 == 0 && m2 == 0)
pDst[x] = 1;
}
}
img &= ~marker;
}
void thinning(const cv::Mat& src, cv::Mat& dst)
{
dst = src.clone();
dst /= 255;
cv::Mat prev = cv::Mat::zeros(dst.size(), CV_8UC1);
cv::Mat diff;
do {
thinningIteration(dst, 0);
thinningIteration(dst, 1);
cv::absdiff(dst, prev, diff);
dst.copyTo(prev);
} while (cv::countNonZero(diff) > 0);
dst *= 255;
}
int main()
{
RNG rng(123);
Mat3b src = imread("path_to_image");
Mat1b gray;
cvtColor(src, gray, COLOR_BGR2GRAY);
Mat1b bin;
threshold(gray, bin, 127, 255, THRESH_BINARY_INV);
thinning(bin, bin);
Mat3b res = src.clone();
vector<vector<Point>> contours;
findContours(bin.clone(), contours, CV_RETR_LIST, CV_CHAIN_APPROX_NONE);
for (vector<Point>& contour : contours)
{
vector<Point> hull;
convexHull(contour, hull);
double area = contourArea(hull);
double perimeter = arcLength(hull, true);
double circularity = (4 * CV_PI * area) / (perimeter * perimeter);
if (circularity > 0.9)
{
{
Point2f center;
float radius;
minEnclosingCircle(contour, center, radius);
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
circle(res, center, radius, color, 5);
}
}
else if (circularity > 0.75)
{
{
Rect box = boundingRect(contour);
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
rectangle(res, box, color, 5);
}
}
else if (circularity > 0.7)
{
Rect roi = boundingRect(contour);
Mat1b maskRoi(bin.rows, bin.cols, uchar(0));
rectangle(maskRoi, roi, Scalar(255), CV_FILLED);
Mat1b triangle(roi.height, roi.height, uchar(0));
bin.copyTo(triangle, maskRoi);
Point2f center;
float radius;
minEnclosingCircle(contour, center, radius);
vector<vector<Point>> vertices;
do
{
vertices.clear();
radius--;
Mat1b maskCirc(bin.rows, bin.cols, uchar(0));
circle(maskCirc, center, radius, Scalar(255), 5);
maskCirc &= triangle;
findContours(maskCirc.clone(), vertices, CV_RETR_LIST, CV_CHAIN_APPROX_NONE);
} while (vertices.size() < 3);
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
line(res, vertices[0][0], vertices[1][0], color, 5);
line(res, vertices[1][0], vertices[2][0], color, 5);
line(res, vertices[2][0], vertices[0][0], color, 5);
}
else
{
cout << "Some other shape..." << endl;
}
}
return 0;
}
结果 (minEnclosingCircle
和 boundingRect
):
结果 (fitEllipse
和 minAreaRect
):