Qt/OpenCV 4.1 感兴趣区域提取及曲线拟合

一、准备

OpenCV 4.1.0 mingw 7.3 自编译版(https://blog.csdn.net/qq_26056015/article/details/93363067

Qt 5.12.4

二、前提

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

三、开发

3.1 灰度化图像

代码:

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算法提取轮廓

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

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

3.3 结果处理

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

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

消除方法是,按照像素对比两张图片,如果直线图中的像素原图中存在就赋值为0,否则保持原图值不变。

查找直线部分:


    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);

消除噪音部分

    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轴像素投影图

投影部分

        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

查找中间位置

    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++;
        }
    }

消除上面部分像素

    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 拟合图像

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

    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);

拟合图像需要拟合点,我采用的是随机取点的方式,随机从图片中取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;
    }

拟合图像

    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);

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

四、测试

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

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

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

qt代码下载:https://share.weiyun.com/53quabG

发布了16 篇原创文章 · 获赞 21 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_26056015/article/details/94735430
今日推荐