12.OpenCV—基础入门

发布于:2025-06-19 ⋅ 阅读:(15) ⋅ 点赞:(0)

01读取图像 

02创建空白图像

03保存图像

04更改图像亮度

05更改图像对比度

06灰度直方图均衡

07彩色直方图均衡

08五种滤波方式

09形态学操作

10仿射变换

11角度+缩放仿射变换

12透视变换

13坐标映射

14模板匹配

15多模板匹配

16查找轮廓线

17轮廓线匹配

17绘制基本图形





.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>


namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();




    void demo01();//01显示图像
    void demo02();//02创建空白图像
    void demo03();//03保存图像
    void demo04();//04更改图像亮度
    void demo05();//05更改图像对比度
    void demo06();//06灰度直方图均衡
    void demo07();//07彩色直方图均衡
    void demo08();//08五种过滤图像
    void demo09(); //09形态学操作
    void demo10();//10仿射平移变换(3点法。2*3矩阵)
    void demo11();//11角度+缩放仿射变换
    void demo12();//12透视变换(4点法 3*3矩阵)
    void demo13(); //13坐标映射(一一对应)
    void demo14(); //14模板匹配
    void demo15();//15多模板匹配
    void demo16();//16查找轮廓线
    void drawRectangle(int event, int x, int y, int flags, void* userdata);



private slots:
    void on_pushButton_clicked();

    void on_pushButton_2_clicked();//轮廓线匹配

    void on_pushButton_3_clicked();//绘制图形

private:
    Ui::MainWindow *ui;
};

#endif // MAINWINDOW_H

.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"


#include <QMessageBox>
#include <QCoreApplication>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
#include <qdebug.h>



MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_pushButton_clicked()
{
    //demo01();//01显示图像
    //demo02();//02创建空白图像
    //demo03();//03保存图像
    //demo04();//04更改图像亮度
    //demo05();//05更改图像对比度
    //demo06();//06灰度直方图均衡
    //demo07();//07彩色直方图均衡
    //demo08();//08五种过滤图像
    //demo09();//09形态学操作
    //demo10();//10仿射变换
    //demo11();//11角度+缩放仿射变换
    //demo12();//12透视变换
    //demo13(); //13坐标映射
    demo14(); //14模板匹配
    //demo15();//15多模板匹配
    //demo16();//16查找轮廓线

}


//01显示图像
void MainWindow::demo01()
{
    /*读取图像Mat imread(const String&filename, int flags = IMREAD_COLOR)
    文件名 - 始终支持 JPEG、JPG、BMP、PNG、TIFF 和 TIF 图像文件类型。支持其他映像文件类型,具体取决于您的平台和安装的编解码器。
    flags - 标志参数有几个可能的值
    默认的IMREAD_COLOR参数。
    IMREAD_UNCHANGED - 图像将按原样加载。如果要在输入图像中获取 alpha 通道(如果可用),则必须使用此标志。
    IMREAD_GRAYSCALE - 图像将作为灰度图像加载(即 - 单通道图像,黑白图像)
    IMREAD_COLOR - 图像将作为BGR图像加载(即 - 3通道图像,彩色图像)*/
    Mat image = imread("./image/1.png",IMREAD_COLOR);

    if(image.empty())
    {
        //报错
        QMessageBox::warning(this,QString::fromLocal8Bit("警告"),QString::fromLocal8Bit("操作错误,某文件不存在"));
        return;
    }else
    {
        //定义一个窗口名称
        String winName = "lena图像";

        /*创建一个窗口用于显示图像
        winname - 窗口的名称。该名称将显示在新创建的窗口的标题栏中。此名称也是此窗口的标识符,它将在以后的 OpenCV 函数调用中用于标识窗口。
        标志 - 确定窗口的大小。在上面的程序中,我没有向此参数传递任何值,因此将使用默认WINDOW_AUTOSIZE参数。
        WINDOW_AUTOSIZE - 用户无法调整窗口大小。图像将以其原始大小显示。
        WINDOW_NORMAL-用户可以调整窗口大小。*/
        namedWindow(winName,WINDOW_AUTOSIZE);

        //把图像放在这个窗口上面
        imshow(winName,image);

        //等待输入任意按键关闭窗口,如果输入大于0的数值则,以毫秒为单位进行倒计时处理
        waitKey(0);
        destroyWindow(winName);
    }
}

//02创建空白图像
void MainWindow::demo02()
{
    /*
    这是 Mat 类中可用的众多构造函数之一。
    它创建一个高 600 像素、宽800像素的图像。
    为图像中的每个像素分配 24 位。
    24 位将由三个无符号 8位整数组成,分别代表蓝色、绿色和红色平面。
    三个整数的值应介于 0 到 255 之间。
    然后,此构造函数使用 3 个无符号整数(100、250、30)初始化创建图像的每个像素。
    因此,它初始化蓝色通道为 100,绿色通道初始化为 250,红色通道初始化为 30。
    由于绿色通道的值明显大于其他通道的值,因此输出图像为绿色。
    Mat:**(int rows, int cols, int type, const Scalar& s)
    */

    Mat image(600,800,CV_8UC3,Scalar(200,31,120));

    String WindowName = "空白图像";

    namedWindow(WindowName);

    imshow(WindowName,image);

    waitKey(0);

    destroyWindow(WindowName);

}

//03保存图像
void MainWindow::demo03()
{
    //读图图像
    Mat image = imread("./image/1.png",IMREAD_COLOR);
    if(image.empty())
    {
        QMessageBox::warning(this,"警告","图像不存在");
        return;
    }

    /*保存读取的图像
        bool imwrite( const String& filename, InputArray img, const std::vector& params = std::vector())
        此函数将给定的 img 对象写入指定的文件。成功后,此函数将返回 true,否则将返回 false。
        1.文件名 - 输出图像的文件名。请注意,文件名的扩展名将用于确定图像格式。
        (例如 - 如果文件名是 MyImage.jpg,则将写入 JPEG 图像。
        如果文件名为 MyImage.png,则将写入 PNG 图像。始终支持 JPEG、JPG、BMP、PNG、TIFF 和 TIF 扩展名。
        2.img - 要保存的图像对象。
            请注意,此图像对象应具有以下属性。
            2.1.图像对象的位深度应为 8 位有符号或 16 位无符号。
            2.2.图像的通道数应为 1 或 3。对于 3 通道图像对象,应存在 BGR 通道顺序。
            2.3.如果图像对象的位深度或通道顺序与上述规范不同,则可以使用 Mat::convertTo 和 cv::cvtColor 函数来转换图像。
        3.参数 - 这是一个可选参数。
     */
    bool isScussess = imwrite("./image/2.png",image);

    //判断是否保存成功
    if(isScussess == false)
    {
        QMessageBox::warning(this,"警告","图像保存失败");
        return;
    }

    //显示一开始读取并保存的图像
    String windowName = "Save image";
    namedWindow(windowName);
    imshow(windowName,image);
    waitKey(0);
    destroyWindow(windowName);

}

//04更改图像亮度
void MainWindow::demo04()
{
    //读图
    Mat image = imread("./image/1.png",IMREAD_COLOR);
    if(image.empty())
    {
        QMessageBox::warning(this,"警告","图像不存在");
        return;
    }

    /*void Mat::convertTo( OutputArray m, int rtype, double alpha=1, double beta=0 ) const
        此函数将每个像素值转换为目标数据类型,并按照以下公式更改值。
        pixel_value_of_output_image(x, y) = pixel_value_of_input_image(x, y) * alpha + beta;
        m - 输出图像。如果需要,将重新分配此数据结构。
        rtype - 输出图像的类型。如果 rtype 为负值,则输出图像的类型将与输入图像的类型相同。
        alpha - 在分配给输出图像之前,输入图像中的每个像素将乘以此数字。
        beta - 此值将添加到输入图像中的每个像素并分配给输出图像。
        */

    //对图像进行亮度增加
    Mat imageAdd;
    image.convertTo(imageAdd,-1,1,100);//亮度增加100

    //对图像进行亮度减少
    Mat imageDark;
    image.convertTo(imageDark,-1,1,-100);//亮度减少100

    //定义窗口名字
    String imageOringWind = "原图像";
    String imageAddWind = "亮度增加50";
    String imageDarkWind = "亮度减少";

    //创建窗口
    namedWindow(imageOringWind,WINDOW_AUTOSIZE);
    namedWindow(imageAddWind,WINDOW_AUTOSIZE);
    namedWindow(imageDarkWind,WINDOW_AUTOSIZE);

    //窗口显示图像
    imshow(imageOringWind,image);
    imshow(imageAddWind,imageAdd);
    imshow(imageDarkWind,imageDark);

    //等待关闭窗口
    waitKey(0);
    destroyWindow(imageOringWind);
    destroyWindow(imageAddWind);
    destroyWindow(imageDarkWind);
}

//05更改图像对比度
void MainWindow::demo05()
{
    //读图
    Mat image = imread("./image/1.png",IMREAD_COLOR);
    if(image.empty())
    {
        QMessageBox::warning(this,"警告","图像不存在");
        return;
    }

    //增加图像对比度(参考亮度)
    Mat imageAdd;
    image.convertTo(imageAdd,-1,4,0);

    //减少图像对比度
    Mat imageReduce;
    image.convertTo(imageReduce,-1,0.4,0);

    //定义窗口名字
    String imageOringWind = "原图像";
    String imageAddWind = "增加图像";
    String imageReduceWind = "减少图像";

    //创建窗口
    namedWindow(imageOringWind,WINDOW_AUTOSIZE);
    namedWindow(imageAddWind,WINDOW_AUTOSIZE);
    namedWindow(imageReduceWind,WINDOW_AUTOSIZE);

    //图像显示
    imshow(imageOringWind,image);
    imshow(imageAddWind,imageAdd);
    imshow(imageReduceWind,imageReduce);

    //等待关闭窗口
    waitKey(0);
    destroyWindow(imageOringWind);
    destroyWindow(imageAddWind);
    destroyWindow(imageReduceWind);

}

//06灰度直方图均衡
void MainWindow::demo06()
{
    //读图
    Mat image = imread("./image/4.png",IMREAD_COLOR);
    if(image.empty())
    {
        QMessageBox::warning(this,"警告","图像不存在");
        return;
    }

    //改变图像颜色变为灰度图(如果一开始读取的就是灰度图,则不需要转化)
    cvtColor(image,image,COLOR_BGR2GRAY);
    //进行灰度直方图均衡化
    Mat hist_equalized_image;
    equalizeHist(image,hist_equalized_image);


    //定义窗口名字
    String imageWind = "原图像";
    String equlizedWind = "均衡化之后";

    //创建窗口
    namedWindow(imageWind,WINDOW_AUTOSIZE);
    namedWindow(equlizedWind,WINDOW_AUTOSIZE);

    //显示图像
    imshow(imageWind,image);
    imshow(equlizedWind,hist_equalized_image);

    //等待关闭窗口
    waitKey(0);
    destroyWindow(imageWind);
    destroyWindow(equlizedWind);
}

//07彩色直方图均衡
void MainWindow::demo07()
{
    // Read the image file
    Mat image = imread("./image/4.png");

    // Check for failure
    if (image.empty())
    {
        QMessageBox::warning(this,"警告","图像不存在");
        return ;
    }

    //把图像从BGR色彩空间转换为YCrCb色彩空间
    /* 直方图均衡只能处理强度信息,不能处理带颜色的通道
        加载的图像位于 BGR 色彩空间中。此颜色空间的 3 个通道(蓝色、绿色和红色)中的任何一个都无法处理以均衡直方图,因为所有通道都包含颜色信息。
        因此,加载的图像应转换为 YCrCb 色彩空间。
        在此颜色空间中,Y 通道仅包含强度信息,而 Cr 和 Cb 通道包含颜色信息。
        因此,只需要处理Y通道即可均衡直方图。
       */
    Mat hist_equalized_image;
    cvtColor(image, hist_equalized_image, COLOR_BGR2YCrCb);//转化图像



    /*把转换好的色彩空间对象分割处对应通道分别是Y,Cr,Cb并把结果存储到vector集合中
        上述 OpenCV 函数将 3 通道图像拆分为 3 个单独的矩阵。
        每个矩阵都被推送到 std::vector。vec_channels[0] 包含 Y 通道,
        vec_channels[1] 包含 Cr 通道,
        vec_channels[2] 包含 Cb 通道。
        */
    vector<Mat> vec_channels;
    split(hist_equalized_image, vec_channels);//分离三通道



    //把拆分出来的Y通道进行直方图均衡
    equalizeHist(vec_channels[0], vec_channels[0]);



    //合并处理好的3通道数据合并到YCrCb的色彩空间中
    merge(vec_channels, hist_equalized_image);//合并三通道



    //把YCrCb的色彩空间图像转换到BGR颜色空间
    cvtColor(hist_equalized_image, hist_equalized_image, COLOR_YCrCb2BGR);




    //Define the names of windows
    String windowNameOfOriginalImage = "原图";
    String windowNameOfHistogramEqualized = "处理后";

    // Create windows with the above names
    namedWindow(windowNameOfOriginalImage, WINDOW_NORMAL);
    namedWindow(windowNameOfHistogramEqualized, WINDOW_NORMAL);

    // Show images inside the created windows.
    imshow(windowNameOfOriginalImage, image);
    imshow(windowNameOfHistogramEqualized, hist_equalized_image);


    waitKey(0); // Wait for any keystroke in one of the windows

    destroyAllWindows(); //Destroy all open windows
}

//08五种过滤图像
void MainWindow::demo08()
{
    //读取图像
    Mat image = imread("./image/1.png",IMREAD_COLOR);
    if(image.empty())
    {
        QMessageBox::warning(this,"警告","图像不存在");
        return;
    }



    //定义窗口名字
    String  imageOriginalWind = "原图";
    String  imageBoxFilterWind = "方框滤波";
    String  imageBlurWind = "均值滤波";
    String  imageGaussianWind = "高斯滤波";
    String  imageMedianWind = "中值滤波";
    String  imageBilateraWind = "双边滤波";

    //创建窗口
    namedWindow(imageOriginalWind);
    namedWindow(imageBoxFilterWind);
    namedWindow(imageBlurWind);
    namedWindow(imageGaussianWind);
    namedWindow(imageMedianWind);
    namedWindow(imageBilateraWind);

    //显示原图
    imshow(imageOriginalWind,image);




    //1.方框滤波处理 –> boxFilter函数来实现 –>线性滤波
    //  方框滤波boxFilter( InputArray src, OutputArray dst, int ddepth, Size ksize, Point anchor = Point(-1,-1),bool normalize = true,int borderType = BORDER_DEFAULT )
    //        src:输入图像
    //        dst:输出图像
    //        ddepth:输入图像的深度,-1 代表使用原图深度
    //        ksize: 滤波内核的大小。一般这样写Size(w, h)来表示内核的大小,Size(10, 10)就表示 10x10 的核大小
    //        anchor = Point(-1,-1) :表示锚点(即被平滑的那个点),注意他有默认值Point(-1,-1) 如果这个点坐标是负值的话,就表示取核的中心为锚点,所以默认值Point(-1,-1)表示这个锚点在核的中心。
    //        normalize = true:默认值为true,一个标识符,表示内核是否被其区域归一化(normalized)了
    //        borderType = BORDER_DEFAULT:用于推断图像外部像素的某种边界模式。有默认值BORDER_DEFAULT,我们一般不去管它。

    Mat imageBoxFilter;
    boxFilter(image,imageBoxFilter,image.depth(),Size(15,15));
    imshow(imageBoxFilterWind,imageBoxFilter);






    //2.均值滤波(邻域平均滤波)–> blur函数 –>线性滤波
    //  均值滤波blur( InputArray src,OutputArray dst, Size ksize, Point anchor = Point(-1,-1),int borderType = BORDER_DEFAULT)
    //        src:输入图像 。
    //        dst:输出图像 。
    //        ksize:内核大小 ,一般用 Size(w,h),w 为宽度,h 为深度。
    //        anchor:被平滑的点,表示取 内核中心 ,默认值 Point(-1,-1)。
    //        boderType:推断图像外部像素的某种边界模式。默认值 BORDER_DEFAULT
    Mat imageBlur;
    blur(image,imageBlur,Size(15,15));
    imshow(imageBlurWind,imageBlur);





    //3.高斯滤波–>GaussianBlur函数 –>线性滤波
    //  高斯滤波GaussianBlur( InputArray src, OutputArray dst, Size ksize,double sigmaX, double sigmaY = 0,int borderType = BORDER_DEFAULT )
    //        src:输入图像 。
    //        dst:输出图像 。
    //        ksize:ksize.width 和 ksize.height 可以不同,但他们都必须为正数和奇数,或者为0,可由 sigma 计算而来
    //        sigmaX:高斯核函数在 X 方向的的标准差
    //        sigmaY:高斯核函数在 Y 方向的的标准差
    //        若 sigmaY 为零,就将它设为 sigmaX;若 sigmaX 和 sigmaY 都是0,那么就由 ksize.width 和 ksize.height 计算出来
    Mat imageGauss;
    GaussianBlur(image,imageGauss,Size(15,15),0);
    imshow(imageGaussianWind,imageGauss);





    //4.中值滤波–>medianBlur函数 –>非线性滤波
    //  中值滤波medianBlur(InputArray src,OutputArray dst,int ksize)
    //        src:输入图像 。
    //        dst:输出图像 。
    //        ksize:孔径的线性尺寸,这个参数必须是大于1 的奇数
    Mat imageMedian;
    medianBlur(image,imageMedian,9);
    imshow(imageMedianWind,imageMedian);






    //5.双边滤波–>bilateralFilter函数 –>非线性滤波
    //  双边滤波bilateralFilter(InputArray src,OutputArray dst, int d,  double sigmaColor,double sigmaSpace, int borderType=BORDER_DEFAULT)
    //        src: 输入图像,可以是Mat类型,图像必须是8位或浮点型单通道、三通道的图像。
    //        dst: 输出图像,和原图像有相同的尺寸和类型。
    //        d: 表示在过滤过程中每个像素邻域的直径范围。如果这个值是非正数,则函数会从第五个参数sigmaSpace计算该值。
    //        sigmaColor: 颜色空间过滤器的sigma值,这个参数的值月大,表明该像素邻域内有月宽广的颜色会被混合到一起,产生较大的半相等颜色区域。
    //        sigmaSpace: 坐标空间中滤波器的sigma值,如果该值较大,则意味着颜色相近的较远的像素将相互影响,从而使更大的区域中足够相似的颜色获取相同的颜色。当d>0时,d指定了邻域大小且与sigmaSpace五官,否则d正比于sigmaSpace.
    //        borderType=BORDER_DEFAULT: 用于推断图像外部像素的某种边界模式,有默认值BORDER_DEFAULT.

    Mat imageBilatera;
    bilateralFilter(image, imageBilatera, 11, 21, 19);
    imshow(imageBilateraWind,imageBilatera);





    //等待关闭窗口
    waitKey(0);
    destroyAllWindows();


}

//09形态学操作
void MainWindow::demo09()
{
    /*
void morphologyEx(InputArray src,OutputArray dst,int op,InputArray kernel,Point anchor = Point(-1,-1),int iterations = 1,int borderType = BORDER_CONSTANT,const Scalar & borderValue = morphologyDefaultBorderValue()
        1.src:输入图像,图像的通道数可以是任意的,但是图像的数据类型必须是CV_8U,CV_16U,CV_16S,CV_32F或CV_64F之一。
        2.dst:形态学操作后的输出图像,与输入图像具有相同的尺寸和数据类型。
        3.op:形态学操作类型的标志,可以选择的标志及含义在表6-6中给出。
        4.kernel:结构元素,可以自己生成,也可以用getStructuringElement()函数生成。
        5.anchor:中心点在结构元素中的位置,默认参数为结构元素的几何中心点
        6.iterations:处理的次数
        7.borderType:像素外推法选择标志,取值范围在表3-5中给出。默认参数为BORDER_DEFAULT,表示不包含边界值倒序填充。
        8.borderValue:使用边界不变外推法时的边界值。

该函数根据结构元素对输入图像进行多种形态学操作,在处理多通道图像时每个通道独立进行处理。
函数的第一个参数为待形态学处理的图像,图像通道数可以是任意的,但是图像的数据类型必须是CV_8U,CV_16U,CV_16S,CV_32F或CV_64F之一。
函数第二个参数为形态学处理后的输出图像,与输入图像具有相同的尺寸和数据类型。
函数第三个参数是形态学操作类型的选择标志,可以选择的形态学操作类型有开运算、闭运算、形态学梯度、顶帽运算、黑帽运算以及击中击不中变换,详细的参数在表6-6给出。
函数第四个和第五个参数都是与结构元素相关的参数,第四个参数为结构元素,使用的结构元素尺寸越大效果越明显,第四个参数为结构元素的中心位置,第五个参数的默认值为Point(-1,-1),表示结构元素的几何中心处为结构元素的中心点。
函数第六个参数是使用结构元素处理的次数,处理次数越多效果越明显。
函数第七个参数是图像像素外推法的选择标志,
第八个参数为使用边界不变外推法时的边界值,这两个参数对图像中主要部分的形态学操作没有影响,因此在多数情况下使用默认值即可。


标志参数         简记       作用

MORPH_ERODE      0       图像腐蚀

MorPh_DIlATE     1       图像膨胀

MORPh_OPEN       2        开运算

MORPH_CLOSE      3        闭运算

MORPH_GRADIENT   4       形态学梯度

MORPh_TOPHAT     5        顶帽运算

MORPH_BLACKHAT   6        黑帽运算

MORPH_HITMISS    7       击中击不中运算

*/


    //用于验证形态学应用的二值化矩阵
    Mat src = (Mat_<uchar>(9, 12) <<
               0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
               0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 0,
               0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
               0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
               0, 255, 255, 255, 0, 255, 255, 255, 0, 0, 0, 0,
               0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
               0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 0,
               0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
               0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);


    namedWindow("src", WINDOW_NORMAL);  //可以自由调节显示图像的尺寸
    imshow("src", src);
    // 创建3X3矩形核元素
    Mat kernel = getStructuringElement(0, Size(3,3));






    // 1.对象矩阵进行膨胀操作
    Mat dilateImg;
    morphologyEx(src, dilateImg, MORPH_DILATE, kernel);
    namedWindow("dilateWd", WINDOW_NORMAL);  //可以自由调节显示图像的尺寸
    imshow("dilateWd", dilateImg);



    // 2.对象矩阵进行腐蚀操作
    Mat erodeImg;
    morphologyEx(src, erodeImg, MORPH_ERODE, kernel);
    namedWindow("erodeWd", WINDOW_NORMAL);  //可以自由调节显示图像的尺寸
    imshow("erodeWd", erodeImg);



    // 3.对象矩阵进行开运算操作
    Mat openImg;
    morphologyEx(src, openImg, MORPH_OPEN, kernel);
    namedWindow("openWd", WINDOW_NORMAL);  //可以自由调节显示图像的尺寸
    imshow("openWd", openImg);





    // 4.对象矩阵进行闭运算操作
    Mat closeImg;
    morphologyEx(src, closeImg, MORPH_CLOSE, kernel);
    namedWindow("closeWd", WINDOW_NORMAL);  //可以自由调节显示图像的尺寸
    imshow("closeWd", closeImg);




    // 5.对象矩阵进行顶帽    原图 - 开操作后的图
    Mat topHatImg;
    morphologyEx(src, topHatImg, MORPH_TOPHAT, kernel);
    namedWindow("topHatWd", WINDOW_NORMAL);  //可以自由调节显示图像的尺寸
    imshow("topHatWd", topHatImg);





    // 6.对象矩阵进行黑帽    闭操作后的图 - 原图
    Mat blackImg;
    morphologyEx(src, blackImg, MORPH_BLACKHAT, kernel);
    namedWindow("blackWd", WINDOW_NORMAL);  //可以自由调节显示图像的尺寸
    imshow("blackWd", blackImg);





    // 7.对象矩阵进行梯度运算操作
    // 内梯度:原图 - 腐蚀图
    // 外梯度:膨胀图 - 原图
    Mat gradientImg;
    morphologyEx(src, gradientImg, MORPH_GRADIENT, kernel);
    namedWindow("gradientWd", WINDOW_NORMAL);  //可以自由调节显示图像的尺寸
    imshow("gradientWd", gradientImg);





    // 8.对象矩阵进行击中击不中运算  通过特定模板,仅当输入的图像中,**有与模板一模一样的块**时,被击中的输入图像区域才会被保留。
    Mat hitmissImg;
    morphologyEx(src, hitmissImg, MORPH_HITMISS, kernel);
    namedWindow("hitmissWd", WINDOW_NORMAL);  //可以自由调节显示图像的尺寸
    imshow("hitmissWd", hitmissImg);



    waitKey(0);
    destroyAllWindows();

}

//10仿射平移变换
void MainWindow::demo10()
{
    //    // 创建一个3X4矩阵
    //       Mat matRect = Mat::eye(3,4, CV_8UC1);
    //       // 打印矩阵
    //       cout << matRect <<endl;
    //       // 打印矩阵的行列值
    //       cout << "rows :" << matRect.rows << endl;
    //       cout << "cols :" << matRect.cols << endl;
    //       // 访问数据
    //       // matRect.at<uchar>(y,x) => y(row), x(col)
    //       matRect.at<uchar>(0,0) = 255;
    //       matRect.at<uchar>(0,2) = 255;
    //       // 设置像素点后结果
    //       cout << matRect <<endl;
    //       namedWindow("matRect", WINDOW_NORMAL);  //可以自由调节显示图像的尺寸
    //       imshow("matRect", matRect);



    // 读取图像
    Mat srcImg = imread("./image/1.png");
    // 定义窗口的名称
    string srcTitle = "原始图像";
    // 创建1个窗口
    namedWindow(srcTitle, WINDOW_NORMAL);
    // 显示原图
    imshow(srcTitle, srcImg);



    // 定义原图的三个点
    Point2f srcPoints[3];
    // 定义映射后的三个坐标点
    Point2f dstPoints[3];



    // 采用三角法
    srcPoints[0] = Point2f(0, 0);
    srcPoints[1] = Point2f(0, srcImg.rows);
    srcPoints[2] = Point2f(srcImg.cols, 0);

    // 映射后的三个点
    dstPoints[0] = Point2f(106, 106);
    dstPoints[1] = Point2f(106, srcImg.rows +106);
    dstPoints[2] = Point2f(srcImg.cols +106,106);



    // 获取仿射变换矩阵
    //        Mat M1=getAffineTransform(const Point2f* src, const Point2f* dst)
    //        参数const Point2f* src:原图的三个固定顶点(左上角,左下角,右上角)
    //        参数const Point2f* dst:目标图像的三个固定顶点
    //        返回值:Mat型变换矩阵,可直接用于warpAffine()函数
    //        注意,顶点数组长度超过3个,则会自动以前3个为变换顶点;数组可用Point2f[]或Point2f*表示
    Mat homat2D = getAffineTransform(srcPoints, dstPoints);



    // 进行图像仿射变换操作
    //void warpAffine(InputArray src, OutputArray dst, InputArray M, Size dsize, int flags=INTER_LINEAR, int borderMode=BORDER_CONSTANT, const Scalar& borderValue=Scalar())
    //参数InputArray src:输入变换前图像
    //参数OutputArray dst:输出变换后图像,需要初始化一个空矩阵用来保存结果,不用设定矩阵尺寸
    //参数InputArray M:变换矩阵,用另一个函数getAffineTransform()计算
    //参数Size dsize:设置输出图像大小
    //参数int flags = INTER_LINEAR:设置插值方式,默认方式为线性插值(另一种WARP_FILL_OUTLIERS)
    //参数int borderMode=BORDER_CONSTANT:边界像素模式,默认值BORDER_CONSTANT
    //参数const Scalar& borderValue=Scalar(),在恒定边界情况下取的值,默认值为Scalar(),即0
    Mat dstImg;
    warpAffine(srcImg, dstImg, homat2D, srcImg.size());

    // 创建1个窗口
    namedWindow("平移后", WINDOW_NORMAL);
    // 显示原图
    imshow("平移后", dstImg);

    waitKey(0);
    destroyAllWindows();


}

//11角度+缩放仿射变换
void MainWindow::demo11()
{
    // 读取图像
    Mat srcImg = imread("./image/1.png");
    // 定窗口的名称
    string srcTitle = "原始图像";

    // 创建1个窗口
    namedWindow(srcTitle, WINDOW_NORMAL);
    // 显示原图
    imshow(srcTitle, srcImg);

    //获取原图坐标
    int nCols = srcImg.cols;
    int nRows = srcImg.rows;


    //设置仿射变换参数
    Point2f centerPoint = Point2f(nCols / 2, nRows / 2);
    double angle = -90;
    double scale = 0.5;


    //获取仿射变换矩阵
    // Mat M2=getRotationMatrix2D (CvPoint2D32f  center,double angle,double scale)
    //参数CvPoint2D32f  center,表示源图像旋转中心
    //参数double angle,旋转角度,正值表示逆时针旋转
    //参数double scale,缩放系数
    Mat homat2D = getRotationMatrix2D(centerPoint, angle, scale);



    // 进行图像仿射变换操作
    Mat dstImg;
    warpAffine(srcImg, dstImg, homat2D, srcImg.size());


    // 创建1个窗口
    namedWindow("变换后", WINDOW_NORMAL);
    // 显示原图
    imshow("变换后", dstImg);

    waitKey(0);
    destroyAllWindows();
}

//12透视变换(四点法的平移仿射变换)
void MainWindow::demo12()
{
    Mat src;
    src = imread("./image/1.png");

    namedWindow("original image", CV_WINDOW_AUTOSIZE);
    imshow("original image", src);

    Mat dst_warp;
    Point2f srcPoints[4];//原图中的四点 ,一个包含三维点(x,y)的数组,其中x、y是浮点型数
    Point2f dstPoints[4];//目标图中的四点


    srcPoints[0] = Point2f(0, 0);
    srcPoints[1] = Point2f(0, src.rows);
    srcPoints[2] = Point2f(src.cols, 0);
    srcPoints[3] = Point2f(src.cols, src.rows);


    //映射后的四个坐标值
    dstPoints[0] = Point2f(100, 100);
    dstPoints[1] = Point2f(100, src.rows+100);
    dstPoints[2] = Point2f(src.cols+100, 100);
    dstPoints[3] = Point2f(src.cols+100, src.rows+100);

    // getPerspectiveTransform(const Point2f* src, const Point2f* dst)
    // 参数const Point2f* src:原图的四个固定顶点
    // 参数const Point2f* dst:目标图像的四个固定顶点
    // 返回值:Mat型变换矩阵,可直接用于warpAffine()函数
    // 注意,顶点数组长度超4个,则会自动以前4个为变换顶点;数组可用Point2f[]或Point2f*表示
    // 注意:透视变换的点选取变为4个
    Mat M1 = getPerspectiveTransform(srcPoints, dstPoints);//由四个点对计算透视变换矩阵


    //  warpPerspective(InputArray src, OutputArray dst, InputArray M, Size dsize, int flags=INTER_LINEAR, int borderMode=BORDER_CONSTANT, const Scalar& borderValue=Scalar())
    //参数InputArray src:输入变换前图像
    //参数OutputArray dst:输出变换后图像,需要初始化一个空矩阵用来保存结果,不用设定矩阵尺寸
    //参数InputArray M:变换矩阵,用另一个函数getAffineTransform()计算
    //参数Size dsize:设置输出图像大小
    //参数int flags = INTER_LINEAR:设置插值方式,默认方式为线性插值(另一种WARP_FILL_OUTLIERS)
    warpPerspective(src, dst_warp, M1, src.size());//仿射变换

    namedWindow("Perspective image", CV_WINDOW_AUTOSIZE);
    imshow("Perspective image", dst_warp);

    waitKey(0);
}

//13坐标映射
void MainWindow::demo13()
{
    Mat src = imread("./image/1.png");
    if (!src.data)
    {
        return ;
    }

    //输出矩阵定义
    Mat result(src.size(), src.type());

    //x方向与y方向矩阵
    Mat xMap(src.size(), CV_32FC1);
    Mat yMap(src.size(), CV_32FC1);


    //取图像的宽和高
    int rows = src.rows;
    int cols = src.cols;

    //图像遍历
    for (int j = 0; j < rows; j++)
    {
        for (int i = 0; i < cols; i++)
        {
            //x与y均翻转
            xMap.at<float>(j, i) = cols - i;
            yMap.at<float>(j, i) = rows - j;
        }
    }
    //重映射操作

    /*
图像的坐标映射是通过原图像与目标图像之间建立一种映射关系,这种映射关系有两种:

一种是计算原图像任意像素在映射后图像后的坐标位置

二是计算变换后图像任意像素反应设在原图像的坐标位置。由原图像映射到目标图像称为正映射,相反地,由目标图像通过映射关系得到原图像称为反映射。

由于正映射常常会映射不完全以及出现映射重复现象,一般在图像处理的过程中采取反映射的方式来保证输出目标图像的每个像素都可以通过映射关系在源图像中找到唯一的对应像素。

OpenCV中提供重映射相关操作,重映射是指把一个图像中一个位置的像素通过映射关系转换到零一图像的指定位置。
对于输入源图像f(x,y),目标图像为g(x,y),映射关系为T,则满足 g(x,y)=T(f(x,y))

需要注意的是通过映射关系T实现得到的目标图像可能存在目标图像像素值是非整数的情况,一般可以考虑插值或向上取整

void remap(InputArray src,OutputArray dst,InputArray map1,InputArray map2,int interpolation,int borderMode=BORDER_CONSTANT,const Scallar& borderValue=Scalar())
        map1表示(x,y)点的坐标或x坐标,可以是CV_16SC2,CV_32FC1,CV_32FC2类型;
        map2表示y坐标,可以是CV_16UC1,CV_32FC1类型,如果map1为(x,y),map2则可以选择不用;
        interpolation表示插值方法;
        borderMode表示边界插值类型;
        borderValue表示插值数值。
*/

    remap(src, result, xMap, yMap, INTER_LINEAR, BORDER_CONSTANT, Scalar(0, 0, 0));
    //输出结果
    imshow("src", src);
    imshow("result", result);
    waitKey();
    destroyAllWindows();
}

//14模板匹配
void MainWindow::demo14()
{
    //    1 CV_EXPORTS_W void matchTemplate(InputArray image, InputArray temp1, OutputArray result, int method);
    //    image:待搜索图像(大图)

    //      temp1:搜索模板,需和原图一样数据类型且尺寸大小不能大于源图像

    //      reuslt:比较结果的映射图像,其必须为单通道的,32位浮点型图像,如果原图(待搜索图像)尺寸为W*H,二temp1的尺寸为w*h,则result的尺寸一定是(W-w+1)*(H-h+1)

    //      method:指定的匹配方法,有如下六种:
    //            CV_TM_SQDIFF --- 平方差匹配法(最好匹配0)
    //            CV_TM_SQDIFF_NORMED --- 归一化平方差匹配法(最好匹配0)
    //            CV_TM_CCORR --- 相关匹配法(最坏匹配0)
    //            CV_TM_CCORR_NORMED ---归一化相关匹配法(最坏匹配0)
    //            CV_TM_CCOEFF --- 系数匹配法(最好匹配1)
    //            CV_TM_CCOEFF_NORMED --- 归一化系数匹配法(最好匹配1)

    //    2、矩阵归一化 — normalize()

    //    void normalize(InputArray src,OutputArray dst, double alpha=1,
    //                double beta=0, int norm_type=NORM_L2, int dtype=-1, InputArray mask=noArray() )
    //      src:输入源图像,Mat类型

    //      dst:输出结果图像,需要和原图一样的尺寸和类型

    //      alpha:归一化后的最小值,默认为1

    //      beta:归一化后的最大值,默认为0

    //      norm_type:归一化类型,可选:NORM_INF, NORM_L1, NORM_L2(默认)等

    //      dtype:默认值为-1,此参数为负值时,输出矩阵和src有同样的类型

    //      mask:可选的掩码操作

    //    normallize()函数的作用是进行矩阵归一化。


    //    3、寻找最值 — minMaxLoc()
    //    1void minMaxLoc(InputArray src, CV_OUT double* minVal, CV_OUT double* maxVal = 0,
    //                      CV_OUT Point* minLoc=0, CV_OUT Point* maxLoc=0,
    //                      InputArray mask=noArray());
    //      src:输入源图像,单通道图像

    //      minVal:返回最小值的指针,若无需返回,则置为0

    //      maxVal:返回最大值的指针,若无需返回,则置为0

    //      minLoc:返回最小位置的指针,若无需返回,则置为0

    //      maxLoc:返回最大位置的指针,若无需返回,则置为0

    //      mask:可选的掩码操作

    //    minMaxLoc()函数的作用是在数组中找到全局最小值和最大值



    Mat temp=imread("./image/20.png");
    Mat src=imread("./image/16.png");
    Mat dst=src.clone();
    imshow("temp",temp);

    int width=src.cols-temp.cols+1;//result宽度
    int height=src.rows-temp.rows+1;//result高度

    Mat result(height,width,CV_32FC1);//创建结果映射图像
    //matchTemplate(srcImg, templateImg, resultImg, CV_TM_SQDIFF); //平方差匹配法(最好匹配0)
    //matchTemplate(srcImg, templateImg, resultImg, CV_TM_SQDIFF_NORMED); //归一化平方差匹配法(最好匹配0)
    //matchTemplate(srcImg, templateImg, resultImg, CV_TM_CCORR); //相关匹配法(最坏匹配0)
    //matchTemplate(srcImg, templateImg, resultImg, CV_TM_CCORR_NORMED); //归一化相关匹配法(最坏匹配0)
    //matchTemplate(srcImg, templateImg, resultImg, CV_TM_CCOEFF); //系数匹配法(最好匹配1)
    matchTemplate(src,temp,result,CV_TM_CCOEFF_NORMED);//化相关系数匹配,最佳值1
  //  imshow("result",result);
    normalize(result,result,0,1,NORM_MINMAX,-1);//归一化到0-1范围

    double minValue,maxValue;
    Point minLoc,maxLoc;
    minMaxLoc(result,&minValue,&maxValue,&minLoc,&maxLoc);
    cout<<"minValue="<<minValue<<endl;
    cout<<"maxValue="<<maxValue<<endl;

    rectangle(dst,maxLoc,Point(maxLoc.x+temp.cols,maxLoc.y+temp.rows),Scalar(0,255,0),2,8);
    imshow("dst",dst);

    waitKey(0);
    destroyAllWindows();
}

//15多模板匹配
void MainWindow::demo15()
{

    Mat srcImg = imread("./image/16.png");
     cvtColor(srcImg, srcImg, CV_BGR2GRAY);//转为灰度图
    Mat templateImg = imread("./image/20.png");
    cvtColor(templateImg, templateImg, CV_BGR2GRAY);//转为灰度图
    Mat resultImg;
    Mat showImg = srcImg.clone();

    int resultImg_cols = srcImg.cols -  templateImg.cols + 1;
    int resultImg_rows = srcImg.rows -  templateImg.rows + 1;

    resultImg.create(resultImg_cols, resultImg_rows, CV_32FC1);
    matchTemplate(srcImg, templateImg, resultImg, CV_TM_CCOEFF_NORMED); //化相关系数匹配法(最好匹配1)
    normalize(resultImg, resultImg, 0, 1, NORM_MINMAX);
    Mat midImg = resultImg.clone();

    //多目标模板匹配---方法一
    double matchValue;
    int count0=0;
    int tempW=0, tempH=0;
    char matchRate[10];

    for(int i=0; i<resultImg_rows; i++)
    {
        for(int j=0; j<resultImg_cols; j++)
        {
            matchValue = resultImg.at<float>(i, j);
            sprintf(matchRate, "%0.2f", matchValue);
            if(matchValue>=0.75 && (abs(j - tempW)>5) && (abs(i - tempH)>5) )
            {
                count0++;
                putText(showImg, matchRate, Point(j-5, i-5), CV_FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255), 1);
                rectangle(showImg, Point(j, i), Point(j + templateImg.cols, i + templateImg.rows), Scalar(0, 255, 0), 2);
                tempW = j;
                tempH = i;
            }
        }
    }
    cout<<"count="<<count0<<endl;
    imshow("resultImg", midImg);
    imshow("dst", showImg);

    //多目标模板匹配---方法二
//           double minValue, maxValue;
//           Point minLoc, maxLoc;
//           Point matchLoc;
//           char matchRate[10];

//           for(int i=0; i<100; i++)
//           {
//               int startX = maxLoc.x - 4;
//               int startY = maxLoc.y - 4;
//               int endX = maxLoc.x + 4;
//               int endY = maxLoc.y + 4;
//               if(startX<0 || startY<0)
//               {
//                   startX = 0;
//                   startY = 0;
//               }
//                if(endX > resultImg.cols - 1 || endY > resultImg.rows - 1)
//               {
//                   endX = resultImg.cols - 1;
//                   endY = resultImg.rows- 1;
//               }
//                Mat temp = Mat::zeros(endX - startX, endY - startY, CV_32FC1);
//                //Mat ROI = resultImg(Rect(Point(startX, startY), temp.cols, temp.rows));
//                temp.copyTo(resultImg(Rect(startX, startY, temp.cols, temp.rows)));
//                minMaxLoc(resultImg, &minValue, &maxValue, &minLoc, &maxLoc);
//                if(maxValue<0.8)    break;

//                cout<<"max_value= "<<maxValue<<endl;
//                sprintf(matchRate, "%0.2f", maxValue);
//                putText(showImg, matchRate, Point(maxLoc.x - 5, maxLoc.y - 5), CV_FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255), 1);
//                rectangle(showImg, maxLoc, Point(maxLoc.x + templateImg.cols, maxLoc.y + templateImg.rows), Scalar(0, 255, 0), 2);

//           }
//           imshow("midImg", midImg);
//           imshow("resultImg", resultImg);
//           imshow("dst", showImg);
    waitKey(0);
    destroyAllWindows();
}

//16查找轮廓线
void MainWindow::demo16()
{/*
    1)什么是轮廓
    轮廓可以简单认为成将连续的点(连着边界)连在一起的曲线,具有相同的颜色或者灰度,提取轮廓就是提取这些具有相同颜色或者灰度的曲线,或者说是连通域,轮廓在形状分析和物体的检测和识别中非常有用。

    2)注意事项:
    ①为了更加准确,要使用二值化图像。在寻找轮廓之前,要进行阈值化处理 或者 Canny 边界检测
    ②查找轮廓的函数会修改原始图像。如果你在找到轮廓之后还想使用原始图像的话,你应该将原始图像存储到其他变量中(clone(), copyTo())
    ③在OpenCV 中,查找轮廓就像在黑色背景中找白色物体。你应该记住,要找的物体应该是白色而背景应该是黑色。


    3)常用函数:
    findContours()—–查找轮廓
    drawContours()—–绘制轮廓

    findContours(InputArray image, OutputArrayofArrays contours, OutputArray hierarchy,int mode, int method, Point offset=Point());

    image: 输入图像, Mat类型8位单通道图像(一般为二值图)
    contours: 检测到的轮廓, 每个轮廓存储为一个点向量, 即Point类型的vector表示
    hierarchy: 可选的输出向量, 包含图像的拓扑信息。其作为轮廓数量的表示, 包含了许多元素, 每个轮廓contours[i]对应4个hierarchy元素hierarchy[i][0]~hierarchy[i][3], 分别表示后一轮廓、前一轮廓、父轮廓、内嵌轮廓的索引编号, 如果没有对应项, 设置为负数

    mode: 轮廓检索模式, 取值如下:
    CV_RETR_EXTERNAL=0-----表示只检测最外层轮廓
    CV_RETR_LIST=1------提取所有轮廓并放置在list中, 轮廓不建立等级关系
    CV_RETR_CCOMP=2------提取所有轮廓并组织为双层结构
    CV_RETR_TREE=3------提取所有轮廓并重新建立网状轮廓结构

    method: 轮廓的近似方法, 取值如下:
    CV_CHAIN_APPROX_NONE ---连续存储所有的轮廓点,任何两个相邻的点都是水平、垂直或者相邻的。也就是说max(abs(x1-x2), abs(y2-y1)) == 1
    CV_CHAIN_APPROX_SIMPLE --- 压缩存储,对于水平、垂直或者斜向的线段,比如一个四边形,只会存储四个顶点
    CV_CHAIN_APPROX_TC89_L1/CV_CHAIN_APPROX_TC89_KCOS

    offset: 每个轮廓的可选偏移量, 默认值Point()



    3、绘制轮廓
    1 CV_EXPORTS_W void drawContours(InputOutputArray image, InputArrayOfArrays contours, int contourIdx,
                                 const Scalar& color, int thickness=1, int lineType=8, InputArray hierarchy=noArray(),
    int maxLevel=INT_MAX, Point offset=Point());
    image: 目标图像, Mat类型对象即可
    contours: 所有的输入轮廓, 每个轮廓存储为一个点向量
    contourIdx: 轮廓绘制指示变量(索引), 若为负值, 则表示绘制所有轮廓
    color: 绘制轮廓的颜色
    thickness: 轮廓线条的粗细, 默认值1, 如果为负值, 则绘制轮廓内部, 可选宏 CV_FILLED
    lineType: 线条类型, 默认值8
    hierarcy: 可选的层次结构信息, 默认值noArray()
    maxLevel: 表示用于绘制轮廓的最大等级, 默认值INT_MAX
    offset: 可选的轮廓偏移参数, 默认值Point()
   */




    //查找所有轮廓线
    //    Mat srcImg = imread("./image/17.png");
    //    Mat tempImg = srcImg.clone();
    //       //Mat draw(srcImg.rows, srcImg.cols, CV_8UC3);
    //       cvtColor(srcImg, srcImg, CV_BGR2GRAY); //转为灰度图
    //       threshold(srcImg, srcImg,100, 255, CV_THRESH_BINARY);//图像二值化,value>threshold(即100)?255:0
    //       imshow("二值化后图像", srcImg);  //轮廓查找前
    //      //imwrite("./image/98.png",srcImg);

    //       vector<vector<Point>> contours;
    //       vector<Vec4i> hierarchy;
    //      // findContours(srcImg, contours, hierarchy,RETR_EXTERNAL, CHAIN_APPROX_SIMPLE ); //查找外轮廓,压缩存储轮廓点
    //       findContours(srcImg, contours, hierarchy,RETR_LIST, CHAIN_APPROX_SIMPLE ); //查找所有轮廓
    //       //findContours(srcImg, contours, hierarchy,CV_RETR_CCOMP, CHAIN_APPROX_SIMPLE ); //查找所有轮廓
    //       //findContours(srcImg, contours, hierarchy,RETR_TREE, CHAIN_APPROX_NONE ); //查找所有轮廓,存储所有轮廓点

    //      // imshow("cont", srcImg);  //轮廓查找后
    //       cout<<"num="<<contours.size()<<endl; //输出轮廓个数

    //       for(int i=0;i<contours.size();i++)
    //       {
    //            drawContours(tempImg, contours,i, Scalar(0, 255, 0),2);  //绘制轮廓:-1代表绘制所有轮廓
    //       }

    //       imshow("原图处理后", tempImg);


    //去除边界轮廓线方法一
    Mat srcImg = imread("./image/16.png");
    Mat tempImg = srcImg.clone();
    cvtColor(srcImg, srcImg, COLOR_BGR2GRAY);
    threshold(srcImg, srcImg, 100, 255, THRESH_BINARY);

    // 进行形态学操作去除小噪点
    Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
    morphologyEx(srcImg, srcImg, MORPH_OPEN, kernel);//开运算

    imshow("二值化后图像", srcImg);

    vector<vector<Point>> contours;
    vector<Vec4i> hierarchy;
    findContours(srcImg, contours, hierarchy, RETR_LIST, CHAIN_APPROX_SIMPLE);

    cout << "num=" << contours.size() << endl;

    double minAreaThreshold1 = 1000.0; // 设置合适的面积阈值
    double minAreaThreshold2 = 15000.0;// 设置合适的面积阈值

    for (int i = 0; i < contours.size(); i++)
    {
        if (contourArea(contours[i]) > minAreaThreshold1 && minAreaThreshold2 >contourArea(contours[i]))
        {
            drawContours(tempImg, contours, i, Scalar(0, 255, 0), 2);
        }
    }

    imshow("原图处理后", tempImg);



    //    //去除边界轮廓线方法二
    //    Mat srcImg = imread("./image/16.png");
    //    Mat tempImg = srcImg.clone();
    //    cvtColor(srcImg, srcImg, COLOR_BGR2GRAY);
    //    threshold(srcImg, srcImg, 100, 255, THRESH_BINARY);
    //    imshow("二值化后图像", srcImg);

    //    vector<vector<Point>> contours;
    //    vector<Vec4i> hierarchy;
    //    findContours(srcImg, contours, hierarchy, RETR_LIST, CHAIN_APPROX_SIMPLE);

    //    cout << "Contours found: " << contours.size() << endl;

    //    // 绘制不接触边界的轮廓
    //    for (int i = 0; i < contours.size(); i++)
    //    {
    //        bool isBoundaryContour = false;
    //        for (auto& point : contours[i])
    //        {
    //            if (point.x <= 0 || point.x >= srcImg.cols - 1 || point.y <= 0 || point.y >= srcImg.rows - 1)
    //            {
    //                isBoundaryContour = true;
    //                break;
    //            }
    //        }

    //        if (!isBoundaryContour)
    //        {
    //            drawContours(tempImg, contours, i, Scalar(0, 255, 0), 2);
    //        }
    //    }

    //    imshow("原图处理后", tempImg);


    waitKey(0);
    destroyAllWindows();
}


//模板匹配
void MainWindow::on_pushButton_2_clicked()
{
    //1.创建模板
    Mat TemplateImage= imread("./image/model/model.png");  //.bmp   加后缀  黑白图
    Mat oriImg = TemplateImage.clone();//复制原图做为最终显示

    Mat TemplateImage1;
    cvtColor(TemplateImage, TemplateImage1, CV_BGR2GRAY);//转为灰度图

    Mat TemplateImage2;
    threshold(TemplateImage1, TemplateImage2, 100, 255,CV_THRESH_BINARY);//二值化处理
    // 进行形态学操作去除小噪点
    Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5));
    morphologyEx(TemplateImage2, TemplateImage2, MORPH_OPEN, kernel);


    vector <vector <Point> >  modelContours;
    vector<Vec4i> hierarcy;
    findContours(TemplateImage2, modelContours, hierarcy,RETR_LIST, CHAIN_APPROX_SIMPLE ); //查找所有轮廓
    qDebug()<<QString::fromLocal8Bit(" 模板轮廓数量:")<<modelContours.size();   //两个轮廓




    double minAreaThreshold1 = 1000.0; // 设置合适的面积阈值
    double minAreaThreshold2 = 20000.0;// 设置合适的面积阈值
    int tempIdx;   //旋转最长的那个轮廓线
    for (int i = 0; i < modelContours.size(); i++)
    {
        if (contourArea(modelContours[i]) > minAreaThreshold1 && minAreaThreshold2 >contourArea(modelContours[i]))
        {
            //绘制查找到的轮廓线
            drawContours(oriImg, modelContours, i, Scalar(0, 255, 0), 2);
            tempIdx = i;
        }
    }

    imshow("模板图像", oriImg);





    //2.找到被查找图像的轮廓线
    Mat SearchImage= imread("./image/model/scene.png");
    Mat oriImg2 = SearchImage.clone();
    imshow("匹配图像", oriImg2);
    Mat TemplateImage3;
    cvtColor(SearchImage, TemplateImage3, CV_BGR2GRAY);//转灰度图
    //pixmap  做个剪切  596 ,  319, 795,  518
    //QPixmap pm = pixmap.copy(QRect(QPoint(596-100 ,  319-50),QPoint(795+200,  518+200)));

    Mat TemplateImage4;
    threshold(TemplateImage3, TemplateImage4, 100, 255,CV_THRESH_BINARY);//二值化

    // 进行形态学操作去除小噪点
    Mat kernel2 = getStructuringElement(MORPH_RECT, Size(7, 7));
    morphologyEx(TemplateImage4, TemplateImage4, MORPH_OPEN, kernel2);


    vector <vector <Point> >  SearchContours;
    vector<Vec4i> hierarcy1;
    findContours(TemplateImage4, SearchContours, hierarcy1,RETR_LIST, CHAIN_APPROX_SIMPLE ); //查找所有轮廓
    qDebug()<<QString::fromLocal8Bit(" 匹配轮廓数量:")<<SearchContours.size();

    double minAreaThreshold3 = 1000.0; // 设置合适的面积阈值
    double minAreaThreshold4 = 20000.0;// 设置合适的面积阈值

    for (int i = 0; i < SearchContours.size(); i++)
    {
        if (contourArea(SearchContours[i]) > minAreaThreshold3 && minAreaThreshold4 >contourArea(SearchContours[i]))
        {
            //绘制查找到的轮廓线
            drawContours(oriImg2, SearchContours, i, Scalar(0, 255, 0), 2);
        }
    }








    //识别到多个变成数组,存到box
    vector<RotatedRect>box(SearchContours.size());
    qDebug()<<QString::fromLocal8Bit("识别数量=")<<SearchContours.size();

    Point center4;//定义匹配坐标
    Mat matCenter4 = Mat::ones(3, 1, CV_64FC1);


    //3.对于被查询轮廓线 逐一进行匹配   ----相当于for循环
    for(int i=0; i<SearchContours.size(); i++)
    {
        //返回的是匹配得分
        //modelContours[tempIdx]  ---模板轮廓线
        //SearchContours[i]   --被检测物体轮廓线

        //对于轮廓线较长进行匹配  ,较小的轮廓线直接删除
        double oriented;
        double area= contourArea(SearchContours[i],oriented);

        if(abs(area)<1000)
        {
            qDebug()<<QString::fromLocal8Bit("短轮廓长度" ) <<abs(area);
            continue;
        }
        else
        {
            qDebug()<<QString::fromLocal8Bit("长轮廓长度")  <<abs(area);
        }

        //开始匹配 matchShapes()函数比较轮廓相似度是基于Hu矩来计算的, 结果越小相似度越高。
        double matchRate = matchShapes(modelContours[tempIdx], SearchContours[i], CV_CONTOURS_MATCH_I1, 0.0);//形状匹配:值越小越相似

        qDebug()<<QString::fromLocal8Bit("匹配得分")<<matchRate ;



        //得分较小的存储并且打印 ,  得分大的直接删除
        if(matchRate<=0.5)
        {

            //仿射轮廓线到目标位置
            drawContours(oriImg2,SearchContours, i, Scalar(0,255,0), 2,8);
            imwrite("./data/sceneResult.bmp",oriImg2);


            /*输出坐标****************************************************/
            //box  ---存储了 匹配的结果
            //RotatedRect::RotatedRect()
            //: center(), size(), angle(0) {}


            box[i]= fitEllipse(Mat(SearchContours[i]));
            //qDebug()<<"matchrate--z1     ---0:="  <<i;
            circle(oriImg2, box[i].center, 3, Scalar(0, 0, 255),-1, 8);





            center4 = box[i].center;


            //string strCenter = pointToString(center4.x,center4.y);
            stringstream ssX,ssY;
            ssX << center4.x;
            ssY << center4.y;

            string sx,sy;
            ssX >> sx;
            ssY >> sy;
            string strCenter = " (" + sx + "," + sy + ")";


            //输出显示
            putText(oriImg2, strCenter, Point2f(center4.x,center4.y), CV_FONT_HERSHEY_COMPLEX, 0.5, Scalar(0, 0, 255),1);


            //保存坐标值
            // matCenter4.at<double>(0, 0) = center4.x;
            //  matCenter4.at<double>(1, 0) = center4.y;

            // 提取值并打印1
            // double x = matCenter4.at<double>(0, 0);
            // double y = matCenter4.at<double>(1, 0);
            // qDebug() << QString::fromLocal8Bit("匹配坐标=")<<"(" << x << "," << y << ")" << endl;


            // 打印2
            qDebug()<<QString::fromLocal8Bit("匹配x坐标")<<center4.x;
            qDebug()<<QString::fromLocal8Bit("匹配y坐标")<<center4.y;
            qDebug()<<QString::fromLocal8Bit("匹配角度")<<box[i].angle;


        }
        imshow("结果图像", oriImg2);
    }
    SearchContours.clear();

    waitKey(0);
    destroyAllWindows();
}


//绘制图形
void MainWindow::on_pushButton_3_clicked()
{
    Mat image(600,800,CV_8UC3,Scalar(255,255,255));
    imshow("空白图像",image);



   //画圆
    Point   ptCircleCenter;  //声明一个变量
    ptCircleCenter.x=300;
    ptCircleCenter.y=300;
    int nRadius=50;

    circle(image, ptCircleCenter, nRadius, Scalar(0, 255, 0), 3);
    imshow("圆",image);


    //画直线
    Point  Pt1,Pt2;
    Pt1.x=150;
    Pt1.y=100;

    Pt2.x=150;
    Pt2.y=500;

    line(image,Pt1,Pt2,Scalar(0,0,0),3,8,0);
    imshow("线",image);



    //画矩形
     Rect rect(370, 200, 300, 300);
    rectangle(image,rect,Scalar(255,0,0),3,2,0);
    imshow("矩形",image);



    // 剪切矩形区域
    Mat croppedImage = image(rect);  // 使用矩形区域进行剪切
    imshow("剪切后的图像", croppedImage);

    waitKey(0);
    destroyAllWindows();

}





网站公告

今日签到

点亮在社区的每一天
去签到