+++
+++
1、代码部分在文件夹reco_poly里面,
2、include/recognize_polygon.h,为头文件;src/recognize_polygon.cpp,为大部分主要的源文件;main.cpp,为main函数的主文件。
3、.vscode文件里放置环境配置的相关文件,此外还有一个CMakeLists.txt。
cvtColor(img_copy, img_copy, COLOR_BGR2GRAY);
Canny(img_copy, img_copy, 20, 50);
morphologyEx(img_copy, img_copy, MORPH_CLOSE, ker);
findContours(img_copy, polygons, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point());
for (int j = 0; j < polygons.size(); j++)
{
vector<Point> result;
double peri = arcLength(polygons[j], true);
approxPolyDP(polygons[j], result, 0.03 * peri, true);
results.push_back(result);
}首先转换为灰度图像,用 Canny 检测边缘,然后进行闭运算,填充边界,用 findContours检测轮廓,用 approxPolyDP进行多边形拟合,最后,将拟合结果存储在 results 中返回。
Point seedPoint(1, 1);
Scalar newVal(255, 255, 255);
int loDiff = 20;
int upDiff = 20;
floodFill(img_copy, seedPoint, newVal, 0, cv::Scalar::all(loDiff), cv::Scalar::all(upDiff));
Mat ker = getStructuringElement(0, Size(3, 3));
cvtColor(img_copy, img_copy, COLOR_BGR2GRAY);
Canny(img_copy, img_copy, 20, 50);
morphologyEx(img_copy, img_copy, MORPH_CLOSE, ker);
findContours(img_copy, polygons, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point());
for (int j = 0; j < polygons.size(); j++)
{
vector<Point> result;
double peri = arcLength(polygons[j], true);
approxPolyDP(polygons[j], result, 0.03 * peri, true);
results.push_back(result);
}首先定义 seedPoint,为需填充的像素值,然后定义 newVal,表示填充白色,接着定义loDiff和upDiff,是阈值的下限和上限,最后使用floodFill 函数,对图像 进行填充。转换为灰度图像,用 Canny 检测边缘,然后进行闭运算,填充边界,用 findContours检测轮廓,用 approxPolyDP进行多边形拟合,最后,将拟合结果存储在 results 中返回。
首先设置了一个RecognizePolygon类用于封装代码
定义了一个TrackParameters结构体,用于后面的追踪条
struct TrackParameters
{
Mat img1;
Mat img2;
int thresholdValue;
int val[10];
vector<vector<Point>> polygons;
};
TrackParameters track;其中img1与img2分别传入的图片与最后得出的图片,thresholdValue与val[10]均为需要调的阈值(下文会一一介绍),polygons用于储存边数信息的容器。
主要用到的函数
threshold(track.img1, mask, track.thresholdValue, 255, THRESH_BINARY_INV);
track.img1.copyTo(dst);
bitwise_not(mask, mask);
dst.setTo(Scalar(0, 0, 0), mask);其中track.thresholdValue即需要滑动条调整的数据,首先将 图像进行阈值处理,存储在 mask 变量中,然后按位取反函数 bitwise_not 对 mask 进行取反最后使用 setTo 将 dst 中,mask 对应的位置像素赋值为全黑色。
Point seedPoint(1, 1);
Scalar newVal(255, 255, 255);
int loDiff = 20;
int upDiff = 20;
floodFill(dst, seedPoint, newVal, 0, Scalar::all(loDiff), Scalar::all(upDiff));首先定义 seedPoint,为需填充的像素值,然后定义 newVal,表示填充白色,接着定义loDiff和upDiff,是阈值的下限和上限,最后使用floodFill 函数,对图像 进行填充。
cvtColor(dst, dst, COLOR_BGR2GRAY);
morphologyEx(dst, dst, MORPH_CLOSE, element);
inRange(dst, Scalar(0, 0, 0), Scalar(254, 254, 254), dst);
Canny(dst, dst, 75, 150);
dilate(dst, track.img2, element2);首先将图像 dst 转换为灰度图像,然后进行闭运算操作,用于去除噪点,接着用 inRange 函数将像素值在 0~254 之间的像素设为白色(255),即排除灰度深浅不一带来的影响,将白色背景以外的所有点变为白色,背景改为黑色,然后用Canny 进行边缘检测,最后膨胀,增强轮廓。
findContours(track.img2, polygonsBegin, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point());
track.polygons.clear();
for (int i = 0; i < polygonsBegin.size(); i++)
{
Rect rect = boundingRect(polygonsBegin[i]);
int area = contourArea(polygonsBegin[i]);
if (area > 600)
{
vector<Point> result;
if (area > 500 && area < 1000)
approxPolyDP(polygonsBegin[i], result, track.val[0], true);
else if (area > 1000 && area < 1500)
approxPolyDP(polygonsBegin[i], result, track.val[1], true);
…………
else if (area > 20000)
approxPolyDP(polygonsBegin[i], result, track.val[9], true);
track.polygons.push_back(result);
vector<Point> foll = {rect.tl(), {rect.tl().x + 50, rect.tl().y}, {rect.tl().x + 50, rect.tl().y - 30}, {rect.tl().x, rect.tl().y - 30}};
fillConvexPoly(track.img2, foll, Scalar(0, 0, 0));
putText(track.img2, "[" + to_string(area) + "]" + to_string(result.size()), rect.tl(), 2, 1, Scalar(255, 255, 255));
}
}
// 显示处理结果
imshow("Threshold Adjust", track.img2);首先进行轮廓检测,并存储在 polygonsBegin 中,然后获取轮廓的面积,去除面积过小的轮廓,然后利用 approxPolyDP 对每个轮廓进行多边形拟合,得到多边形轮廓信息,并加入到 track.polygons 容器中,在拟合多边形的过程中,使用不同的精度参数对不同大小的轮廓进行拟合,以获得更加准确的结果。
最后,使用 fillConvexPoly 和 putText 在处理图像中标出多边形的顶点个数和面积,便于调试。
该步的目的是对目标进行拟合,并将拟合的多边形轮廓信息存储在 track.polygons 容器中,以便进行后续的目标跟踪和识别工作。同时,在处理图像中标出多边形顶点和面积信息,方便调试。最后,通过 imshow 函数显示处理结果。
void RecognizePolygon::find()
{
namedWindow("Threshold Adjust", WINDOW_AUTOSIZE);
namedWindow("ApproxPolyDP Adjust", WINDOW_AUTOSIZE);
track.img1 = img;
track.thresholdValue = 200;
track.val[0] = 4;
track.val[1] = 5;
…………
track.val[9] = 15;
createTrackbar("Threshold:", "ApproxPolyDP Adjust", &track.thresholdValue, 255, trackThreshold);
createTrackbar("<1000", "ApproxPolyDP Adjust", &track.val[0], 20, trackThreshold);
createTrackbar("<1500", "ApproxPolyDP Adjust", &track.val[1], 20, trackThreshold);
…………
createTrackbar(">20000:", "ApproxPolyDP Adjust", &track.val[9], 20, trackThreshold);
trackThreshold(0, NULL);
waitKey(0);
results = track.polygons;
}
主要是用 createTrackbar创建用于调整拟合参数的滑块条,以便实时调整二值化阈值和拟合精度参数。将结果保存到 results 中。
string RecognizePolygon::getShapeType(int numSides)
{
string type;
switch (numSides)
{
case 6:
type = "Poly-6";
break;
case 7:
type = "Poly-7";
break;
case 8:
type = "Poly-8";
break;
default:
type = "star";
break;
}
return type;
}int counts[4] = {0};
for (int i = 0; i < results.size(); i++)
{
Rect rect = boundingRect(results[i]);
rectangle(img, rect, Scalar(255, 255, 255), 2, 8, 0);
string type = getShapeType(results[i].size());
putText(img, type, rect.tl(), 2, 1, Scalar(255, 255, 255));
if (results[i].size() == 6 || results[i].size() == 7 || results[i].size() == 8)
{
counts[results[i].size() - 6]++;
}
else
{
counts[3]++;
}
}定义长为 4 的数组统计多边形类型的数目,并用rectangle和putText将结果打印到图片上,接着循环判断当前多边形的类型,记录在数组中。
for (int j = 0; j < 4; j++)
{
if (counts[j] > 0)
{
string type = getShapeType(j + 6);
cout << type << ": " << counts[j] << endl;
}
}1、本人试过很多种方法对轮廓进行预处理,包括中值滤波;split成三个通道分别识别再合成,以及二值化等等,最后上述图像预处理的方法,是在本人尝试过的方法中轮廓最清晰的一种。
2、即使是用上述方法,依旧会毁坏部分轮廓信息,于是想出了在预处理以外的地方下功夫,即调整拟合多边形的精度,但是一个精度并不足以拟合所有多边形,考虑到多边形大小不一,所以根据面积划分了多个精度,并一一调式,初见成效。
3、但是这也并不能保证完全识别成功,于是想出了运用滑动条,实时根据识别效果调整精度,并且可以根据多边形的面积对应调整,这样基本能保证完全识别成功。
4、优点:在预处理的情况下,需要调整精度的多边形一般只有一两个,大大提升效率;又有滑动条保证其能调整到比较合适的精度,大大提高正确率。
5、缺点:我认为在需要调参数这一点还是会拖慢速度,但是本人至今没有想到如何用一套参数快速地识别得到结果。