OpenCV4入门系列教程90.5:测试7感兴趣区域提取及曲线拟合

入门系列索引:系列教程索引地址

上一篇:OpenCV4入门系列教程90:轮廓拟合(直线/圆与椭圆)

一、准备

OpenCV 4.1.0 mingw 7.3 自编译版

Qt 5.12.4

二、前提

公司给出题目提取下面图片中中间的部分,并绘出拟合曲线。

src

三、开发

3.1 灰度化图像

gray

代码:

1
2
3
4
5
6
7
8
9
cv::Mat grayImage(Mat srcImage)
{
Mat grayImage, tempImage;
cvtColor(srcImage, tempImage, COLOR_RGB2GRAY);
tempImage.copyTo(grayImage);
tempImage.release();
imwrite("gray.png",grayImage);
return grayImage;
}

3.2 采用canny算法提取轮廓

canny

1
2
3
cv::Mat contours;
cv::Canny(gray,contours,100,150);
cv::imshow("canny",contours);

其中参数是我以50为间隔,测试出来的

3.3 结果处理

可以看出中间的部分就是我需要的,但是左右两边和上方包含噪音,需要去除。

target

考虑到直线,那么就先查找所有的直线,然后从原图中删除直线,顺便把x直线周围的两个像素一起删除

lines

消除方法是,按照像素对比两张图片,如果直线图中的像素原图中存在就赋值为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
cv::Mat linesImg = cv::Mat::zeros(contours.rows,contours.cols,contours.type());
std::vector<cv::Vec2f> lines;
cv::HoughLines(contours,lines,1,PI/180,60);
std::vector<cv::Vec2f>::const_iterator it = lines.begin();
while(it!=lines.end()){
float rho = (*it)[0];
float theta = (*it)[1];
if (theta<PI / 4.0 || theta>3.0*PI / 4.0)
{
//直线与第一行的交叉点
cv::Point pt1(static_cast<int>(rho / cos(theta)), 0.0);
//直线与最后一行的交叉点
cv::Point pt2(rho / cos(theta) - contours.rows*sin(theta) / cos(theta), contours.rows);
cv::line(linesImg, pt1, pt2, cv::Scalar(255,255,255), 1);
}
else
{
cv::Point pt1(0, rho / sin(theta));
cv::Point pt2(contours.cols, rho / sin(theta) - contours.cols*cos(theta) / sin(theta));
cv::line(linesImg, pt1, pt2, cv::Scalar(255,255,255), 1);
}
it++;
}

imshow("lines",linesImg);

消除噪音部分

1
2
3
4
5
6
7
8
9
10
11
12
uchar *pcontour,*plines;
for(int i=0;i<contours.rows;i++){
pcontour = contours.ptr<uchar>(i);
plines = linesImg.ptr<uchar>(i);
for(int j=0;j<contours.cols;j++){
if(pcontour[j]==plines[j]){
pcontour[j]=0;
pcontour[j-1]=0;
}
}
}
cv::imshow("new result",contours);

然后图片就剩下两部分,下面的那一部分是我需要的

处理部分是,生成y轴像素投影图

y

投影部分

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
vector<Mat> GetHorizontalProjection(Mat src,Mat &dst)
{
vector<Mat>rois;
dst = Mat::zeros(src.size(), CV_8UC1);
vector<int>vectorH;
vector<int>HUp;
vector<int>HDown;
//水平投影
for (int j = 0;j < src.rows;j++)
{
Mat data = src.row(j);
int iCount = countNonZero(data);
vectorH.push_back(iCount);
}
//分析
for (int k = 1;k < vectorH.size();k++)
{
if (vectorH[k - 1] == 0 && vectorH[k] > 0)
HUp.push_back(k);
if (vectorH[k - 1] > 0 && vectorH[k] == 0)
HDown.push_back(k);
}
//绘制图像
for (int i = 0;i < vectorH.size();i++)
{
if (vectorH[i] != 0)
line(dst, Point(dst.cols,i ), Point(dst.cols - vectorH[i],i), Scalar(255));
}

//根据波峰波谷提取roi区域
for (int i = 0;i < HUp.size();i++)
{
Mat roi = src(Rect(Point(0, HUp[i]), Size(src.cols, HDown[i] - HUp[i])));
rois.push_back(roi);
}

return rois;
}

可以看出中间部分有一段空白区域,以此为分界线,从下往上读取水平投影图,如果读取到20行空白值则把空白值以上部分的像素全部置为0

lines

查找中间位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
bool status=false;
int count =0;
int position =0;
for(int i=temp.rows-1;i>=0;i--){
Mat data = temp.row(i);
int iCould = cv::countNonZero(data);

if(iCould){
if(count>=20){
position=i;
//cv::line(contours,Point2d(0,i),Point2d(contours.cols,i),Scalar(255,255,255),1,8);
break;
}
else {
count=0;
status=true;
}
}
else if(iCould == 0 && status){
count++;
}
}

消除上面部分像素

1
2
3
4
5
6
7
8
uchar *p;
for(int i=0;i<position;i++){
p = contours.ptr<uchar>(i);
for(int j=0;j<contours.cols;j++){
p[j]=0;
}
}
imshow("canny result",contours);

3.4 拟合图像

可以看出,提取的图像左右对称,为了方便计算,我们取其中一半来处理。

1
2
3
4
5
Mat half = img(Rect(0,0,img.cols/2,img.rows));
qDebug()<<"half width is: "<<half.cols<<" height is: "<<half.rows;
Mat t = half.clone();
imshow("t",t);
//imshow("half",half);

half

拟合图像需要拟合点,我采用的是随机取点的方式,随机从图片中取25个有效点

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
int pCount=0;
RNG rng;
uchar *p;
vector<Point> key_point;
do{
int x = rng.uniform(0,half.cols);
qDebug()<<"x="<<x;
p = half.ptr<uchar>(x);
for(int y=0;y<half.rows;y++){
if(p[y]!=0){
qDebug()<<"y="<<y;
key_point.push_back(Point(y,x));
circle(half,Point(y,x),3,Scalar(255,255,255),3,8,0);
pCount++;
y=half.rows-2;
}
}

}while(pCount<=25);

imshow("half",half);

for(int i=0;i<key_point.size();i++){
std::cout<<key_point.at(i)<<std::endl;
}

拟合图像

points

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cv::Mat A;
detect->polynomial_curve_fit(key_point,3,A);

std::vector<cv::Point> points_fitted;
std::cout<<A<<std::endl;

for (int x = 0; x < 400; x++)
{
double y = A.at<double>(0, 0) + A.at<double>(1, 0) * x +
A.at<double>(2, 0)*std::pow(x, 2) + A.at<double>(3, 0)*std::pow(x, 3);

points_fitted.push_back(cv::Point(x, y));
}
cv::polylines(t, points_fitted, false, cv::Scalar(255, 255, 255), 1, 8, 0);
imshow("t",t);

可以看出虽然左侧有点不符合,但是近似,可能是点的位置有点问题

四、测试

公司给了三张图片,我们用另外两张来测试

test1
test2

可以看出第二张图片因为拟合点的位置,曲线与原图有差异待改进。

参考资料太多,就不写了,基本上都是CSDN上面的,主要是整合。

qt代码下载:腾讯微云