导航索引帖
前置文章,课设第五篇
上篇文章中,已经完成了视频的处理工作,大部分工作出于流畅度考虑,使用了opencv的方式来处理,本篇将进行最后的一项功能,硬币检测功能。
本文这里将使用两种方法进行硬币检测算法;
分水岭比较经典的计算方法是L.Vincent于1991年在PAMI上提出的[1]。传统的分水岭分割方法,是一种基于拓扑理论的数学形态学的分割方法,其基本思想是把图像看作是测地学上的拓扑地貌,图像中每一像素的灰度值表示该点的海拔高度,每一个局部极小值及其影响区域称为集水盆地,而集水盆地的边界则形成分水岭。分水岭的概念和形成可以通过模拟浸入过程来说明。在每一个局部极小值表面,刺穿一个小孔,然后把整个模型慢慢浸人水中,随着浸入的加深,每一个局部极小值的影响域慢慢向外扩展,在两个集水盆汇合处构筑大坝如下图所示,即形成分水岭。
然而基于梯度图像的直接分水岭算法容易导致图像的过分割,产生这一现象的原因主要是由于输入的图像存在过多的极小区域而产生许多小的集水盆地,从而导致分割后的图像不能将图像中有意义的区域表示出来。所以必须对分割结果的相似区域进行合并。
因为传统分水岭算法存在过分割的不足,OpenCV提供了一种改进的分水岭算法,使用一系列预定义标记来引导图像分割的定义方式。使用OpenCV的分水岭算法cv::wathershed,需要输入一个标记图像,图像的像素值为32位有符号正数(CV_32S类型),每个非零像素代表一个标签。它的原理是对图像中部分像素做标记,表明它的所属区域是已知的。分水岭算法可以根据这个初始标签确定其他像素所属的区域。传统的基于梯度的分水岭算法和改进后基于标记的分水岭算法示意图如下图所示
从上图可以看出,传统基于梯度的分水岭算法由于局部最小值过多造成分割后的分水岭较多。而基于标记的分水岭算法,水淹过程从预先定义好的标记图像(像素)开始,较好的克服了过度分割的不足。本质上讲,基于标记点的改进算法是利用先验知识来帮助分割的一种方法。因此,改进算法的关键在于如何获得准确的标记图像,即如何将前景物体与背景准确的标记出来。
//分水岭
void MainWindow::on_pushButton_7_clicked()
{
Mat gray, thresh;
Mat img = coinimage;
//灰度化
cvtColor(img, gray, COLOR_BGR2GRAY);
//二值化
threshold(gray, thresh, 0, 255, THRESH_BINARY_INV+CV_THRESH_OTSU);
//侵蚀
Mat opening; Mat sure_bg;
Mat sure_fg; Mat unknow;
Mat dist_transform;
double maxValue;
// noise removal
Mat kernel = Mat::ones(3, 3, CV_8U);
morphologyEx(thresh, opening, MORPH_OPEN, kernel);
// sure background area
dilate(opening, sure_bg, kernel, Point(-1, -1), 3);
// Finding sure foreground area
distanceTransform(opening, dist_transform, DIST_L2, 5);
minMaxLoc(dist_transform, 0, &maxValue, 0, 0);
threshold(dist_transform, sure_fg, 0.7*maxValue, 255, 0);
// Finding unknown region
sure_fg.convertTo(sure_fg, CV_8U);
subtract(sure_bg, sure_fg, unknow);
// Marker labelling
Mat markers;
connectedComponents(sure_fg, markers);
// Add one to all labels so that sure background is not 0, but 1
markers = markers + 1;
// Now, mark the region of unknown with zero
markers.setTo(0, unknow);
Mat marker;
Mat mask;
watershed(img, markers);
compare(markers, -1, mask, CMP_EQ);
img.setTo(Scalar(0, 0, 255), mask);
QImage outputpic =MatToQImage(img);
outputpic=watermark(outputpic);
ui->coinlabel->setPixmap(QPixmap::fromImage(ImageSetSize(outputpic,ui->coinlabel)));
}
可以看到,除了硬币轮廓外,图中其他的轮廓线条也很多,这种方式用于边缘检测效果并不尽如人意。
在霍夫圆变换中,需要考虑圆半径和圆心(x坐标、y坐标)共3个参数。在OpenCV中,采用的策略是两轮筛选。第1轮筛选找出可能存在圆的位置(圆心);第2轮再根据第1轮的结果筛选出半径大小。
与用来决定是否接受直线的两个参数“接受直线的最小长度(minLineLength)”和“接受直线时允许的最大像素点间距(MaxLineGap)”类似,霍夫圆变换也有几个用于决定是否接受圆的参数:圆心间的最小距离、圆的最小半径、圆的最大半径。
但这样的算法也有很大的问题即圆的半径大小需要人为预处理。
//霍夫曼圆
void MainWindow::on_HoughBtn_clicked()
{
Mat src=coinimage;
Mat grayImg;
cvtColor(src, grayImg, CV_BGR2GRAY);
vector<Vec3f>circles;
int hough_value = 80;
HoughCircles(grayImg, circles, HOUGH_GRADIENT, 1, 10, 110, hough_value, 10, 100);
Mat houghcircle = src.clone();
for (int i = 0; i < circles.size(); i++) {
circle(houghcircle, Point(circles[i][0], circles[i][1]), circles[i][2], Scalar(0, 0, 255), 2);
}
QImage outputpic =MatToQImage(houghcircle);
outputpic=watermark(outputpic);
ui->coinlabel->setPixmap(QPixmap::fromImage(ImageSetSize(outputpic,ui->coinlabel)));
}
目前考虑用yolo+pytorch的方案进行图像切割的机器学习方案;出于时间考虑暂时无法完成,寒假如果有继续开展工作的话将更新在本系列中。
添加水印的原理其实很简单,水印图的内容读取后加载到原图上方描点就行,这里不贴图了,涉及部分个人隐私。
//水印
QImage MainWindow::watermark(QImage img)
{
QImage simage("E:/2022QT/Work/name3.png");//这里换成你自己的水印路径
int swidth = simage.width();
int sheight = simage.height();
int r,b,g;
for(int i=0; i<sheight; ++i) {
for(int j=0; j<swidth; ++j) {
QColor oldcolor2=QColor(simage.pixel(j,i));
r=oldcolor2.red();
b=oldcolor2.blue();
g=oldcolor2.green();
if(r==0&&b==0&&g==0)
{
img.setPixelColor(j,i,qRgb(0,0,0));
}else
{
//image.setPixelColor(j,i,qRgb(red,blue,green));
}
}
}
return img;
}