本人正在学习 OpenCV 计算机视觉相关知识,此篇为个人学习笔记,参考书籍为冯振等人的《OpenCV4 快速入门》,主要记录 OpenCV 在 Linux 环境下的编译、运行实操流程与相关知识点总结。

笔记内容均为亲自实操验证,过程中踩过不少编译配置、运行参数的坑,整理出来一方面用于自己复盘巩固,另一方面也希望能帮助到刚入门 OpenCV、在编译运行示例程序时遇到问题的小伙伴。文中内容如有疏漏或错误之处,欢迎各位大佬评论区指正交流,共同进步~

第三章 图像基本操作

3.1 图像颜色空间

3.1.1 颜色模型与转换

  1. RGB颜色模型+A模型(A模型表示透明度)

opencv:BGR(蓝绿红)某个通道都表示颜色0-1的过程

白色:0,0,0 黑色:255,255,255

  1. YUV颜色模型:电视信号系统采用的颜色编码方式,用于视频和图像的传输

Y:表示像素的亮度,U:表示红色;分量和亮度的信号差值,V:蓝色与亮度差值

彩色电视需要3通道(通过YUV转变成RGB),黑白电视只需1通道

转换关系式:

U = -0.147R-0.289G +0.436B

V = 0.615R-0.515G-0.100B

R=Y+1.14V

G=Y-0.39U-0.58V

B=Y+2.03U

  1. HSV颜色模型

锥形结构。H:色度(平时说的颜色) S:饱和度(颜色的纯度,越高越艳,,,范围0-100%) V:亮度(颜色的明亮程度,,,范围0-max)

  1. Lab颜色模型

球形结构。L是亮度,a\b是两个颜色通道,,,范围-128~127; a通道从小到大是绿到红,b通道小到大是蓝到黄

  1. GRAY颜色模型

灰度模型。0~max表示黑到白,255表示白色

RGB转换GRAY:GRAY=0.3R+0.59G+0.11B

  1. 不同颜色模型转换

src:待转换颜色模型的原始图像

dst:转换颜色模型后的目标图像

code:颜色空间转换的标志,由RGB空间到HSV空间

dstCn:目标图像中的通道数。参数为0,自动导出

void cv::cvtColor(InputArray src,
                    OutputArray dst,
                    int code,
                    int dstCn = 0
                   )

8位无符号图像像素0-255,16位图像像素0-65535,32位图像像素0-1.线性变换不需要考虑,非线性变换需要归一化

示例:

#include <opencv2/opencv.hpp>  // 引入OpenCV核心头文件,包含所有基础图像处理函数
#include <iostream>            // 引入输入输出流头文件,用于打印错误信息
#include <vector>              // 引入向量容器头文件(本段代码未实际使用,属于冗余引入)
using namespace std;           // 使用std命名空间,避免每次写cout、vector等都加std::
using namespace cv;            // 使用cv命名空间,避免每次写Mat、imread等都加cv::

int main()                     // 主函数,程序入口
{
    // 1. 读取图像文件
    Mat img = imread("lena.png");  // imread函数读取图片,返回Mat类型(OpenCV的图像容器)
    if (img.empty())               // 检查图像是否读取成功(文件不存在/路径错误/格式不支持都会返回空)
    {
        cout << "请确认图像文件名称是否正确" << endl;  // 打印错误提示
        return -1;                                     // 异常退出程序
    }

    // 2. 定义图像容器,存储不同格式/色彩空间的图像
    Mat gray, HSV, YUV, Lab, img32;  
    // gray:灰度图容器;HSV/YUV/Lab:对应色彩空间容器;img32:32位浮点型原图容器

    // 3. 图像数据类型转换
    img.convertTo(img32, CV_32F, 1.0 / 255);  
    // convertTo:将img的类型转换后存入img32
    // 参数1:目标图像容器(img32)
    // 参数2:目标数据类型(CV_32F,32位浮点型)
    // 参数3:缩放因子(1/255):将原CV_8U类型的0-255整数,缩放到0.0-1.0的浮点数范围
    // 注释行:反向转换(将32位浮点型转回8位无符号整型,乘以255还原0-255范围)

    // 4. 色彩空间转换
    cvtColor(img32, HSV, COLOR_BGR2HSV);   // 将32位浮点型BGR图转为HSV色彩空间
    cvtColor(img32, YUV, COLOR_BGR2YUV);   // 转为YUV色彩空间
    cvtColor(img32, Lab, COLOR_BGR2Lab);   // 转为Lab色彩空间
    cvtColor(img32, gray, COLOR_BGR2GRAY); // 转为灰度图(单通道)

    // 5. 显示图像
    imshow("原图", img32);  // 显示32位浮点型原图
    imshow("HSV", HSV);     // 显示HSV色彩空间图像
    imshow("YUV", YUV);     // 显示YUV色彩空间图像
    imshow("Lab", Lab);     // 显示Lab色彩空间图像
    imshow("gray", gray);   // 显示灰度图

    waitKey(0);  // 等待按键输入(参数0表示无限等待),否则图像窗口会一闪而过
    return 0;    // 程序正常退出
}

  • convertTo()函数 数据类型转换函数

例如: img.convertTo(img32, CV_32F, 1.0 / 255);

// convertTo:将img的类型转换后存入img32

// 参数1:目标图像容器(img32)

// 参数2:目标数据类型(CV_32F,32位浮点型)

// 参数3:缩放因子(1/255):将原CV_8U类型的0-255整数,缩放到0.0-1.0的浮点数范围

转换公式将原有数据进行线性转换

3.1.2 多通道分离与合并

  • split()函数分离函数:将多通道图像分离成单通道图像

src:待分离的多通道图像

mvbegin: 分离后的单通道图像,为数组形式

m:待分离的多通道图像

mv:分离后的单通道图像,为向量形式

void cv::split(const Mat & src,
                Mat *  mvbegin
                )
void cv::split(InputArray m,
                OutputArrayOfArrays mv
                )
  • merge()函数合并函数

mv:需要合并的图像数组

count:输入的图像数组长度,数值必须大于0

dst:合并后输出的图像,通道数等于所有输入图像的通道数总和。

void cv::merge(const Mat * mv,
                size_t count,
                OutputArray dst)
void cv::merge(InputArrayOfArrays mv,
                OutputArray dst)

示例:

#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
using namespace std;
using namespace cv;

int main()
{
    Mat img = imread("lena.png");
    if (img.empty())
    {
        cout << "请确认图像文件名称是否正确" << endl;
        return -1;
    }
    Mat HSV;
    cvtColor(img, HSV, COLOR_RGB2HSV);
    Mat imgs0, imgs1, imgs2;  //用于存放数组类型的结果
    Mat imgv0, imgv1, imgv2;  //用于存放vector类型的结果
    Mat result0, result1, result2;  //多通道合并的结果

                                    //输入数组参数的多通道分离与合并
    Mat imgs[3];
    split(img, imgs);
    imgs0 = imgs[0];
    imgs1 = imgs[1];
    imgs2 = imgs[2];
    imshow("RGB-B通道", imgs0);  //显示分离后B通道的像素值
    imshow("RGB-G通道", imgs1);  //显示分离后G通道的像素值
    imshow("RGB-R通道", imgs2);  //显示分离后R通道的像素值
    imgs[2] = img;  //将数组中的图像通道数变成不统一
    merge(imgs, 3, result0);  //合并图像
                              //imshow("result0", result0);  //imshow最多显示4个通道,因此结果在Image Watch中查看
    Mat zero = cv::Mat::zeros(img.rows, img.cols, CV_8UC1);
    imgs[0] = zero;
    imgs[2] = zero;
    merge(imgs, 3, result1);  //用于还原G通道的真实情况,合并结果为绿色
    imshow("result1", result1);  //显示合并结果
    //输入vector参数的多通道分离与合并
    vector<Mat> imgv;
    split(HSV, imgv);
    imgv0 = imgv.at(0);
    imgv1 = imgv.at(1);
    imgv2 = imgv.at(2);
    imshow("HSV-H通道", imgv0);  //显示分离后H通道的像素值
    imshow("HSV-S通道", imgv1);  //显示分离后S通道的像素值
    imshow("HSV-V通道", imgv2);  //显示分离后V通道的像素值
    imgv.push_back(HSV);  //将vector中的图像通道数变成不统一
    merge(imgv, result2);  //合并图像
                           //imshow("result2", result2);  /imshow最多显示4个通道,因此结果在Image Watch中查看
    waitKey(0);
    return 0;
}

3.2 图像像素操作处理

3.2.1 图像像素统计

  • 寻找图像像素最大值和最小值--minMaxLoc()函数

src:寻找最大值和最小值的图像或矩阵,必须是单通道矩阵

minVal:图像或者矩阵中的最小值

maxVal:图像或者矩阵中的最大值

minLoc:图像或者矩阵的最小值在矩阵中的坐标

mask:掩模,用于设置在图像或矩阵中的指定区域寻找最值

point:用于表示图像的像素坐标,以左上角为坐标原点,水平方向为x轴,垂直方向为y轴

注意:如果是多通道,需要cv::Mat::reshape()将多通道转换为单通道

void cv::minMaxLoc(InputArray src,
                    double * minVal,
                    double * maxVal = 0,
                    Point * minLoc = 0,
                    Point * maxLoc = 0,
                    InputArray mask = noArray()
                    )
  • cv::Mat::reshape()函数原型:

cn:转换后矩阵的通道数

rows:转化后矩阵的行数

Mat cv::Mat::reshape(int cn,  int rows = 0)
#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace std;
using namespace cv;

int main()
{
    system("color F0");  //更改输出界面颜色
    float a[12] = { 1, 2, 3, 4, 5, 10, 6, 7, 8, 9, 10, 0 };
    Mat img = Mat(3, 4, CV_32FC1, a);  //单通道矩阵
    Mat imgs = Mat(2, 3, CV_32FC2, a);  //多通道矩阵
    double minVal, maxVal;  //用于存放矩阵中的最大值和最小值
    Point minIdx, maxIdx;  ////用于存放矩阵中的最大值和最小值在矩阵中的位置

    /*寻找单通道矩阵中的最值*/
    minMaxLoc(img, &minVal, &maxVal, &minIdx, &maxIdx);
    cout << "img中最大值是:" << maxVal << "  " << "在矩阵中的位置:" << maxIdx << endl;
    cout << "img中最小值是:" << minVal << "  " << "在矩阵中的位置:" << minIdx << endl;

    /*寻找多通道矩阵中的最值*/
    Mat imgs_re = imgs.reshape(1, 4);  //将多通道矩阵变成单通道矩阵
    minMaxLoc(imgs_re, &minVal, &maxVal, &minIdx, &maxIdx);
    cout << "imgs中最大值是:" << maxVal << "  " << "在矩阵中的位置:" << maxIdx << endl;
    cout << "imgs中最小值是:" << minVal << "  " << "在矩阵中的位置:" << minIdx << endl;
    return 0;
}
  • 寻找平均值和标准差 mean函数和meanStdDev()函数

平均值表示图像整体的亮暗程度,图像的平均值越大,越亮

标准差表示图像明暗变化的对比程度,标准差越大,明暗变化越明显

mean函数:只能求每个通道的平均值

meanStdDev()函数:同时求取平均值和标准差。没有返回值,输出在第二/三参数

示例:

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace std;
using namespace cv;

int main()
{
    system("color F0");  //更改输出界面颜色
    float a[12] = { 1, 2, 3, 4, 5, 10, 6, 7, 8, 9, 10, 0 };
    Mat img = Mat(3, 4, CV_32FC1, a);  //单通道矩阵
    Mat imgs = Mat(2, 3, CV_32FC2, a);  //多通道矩阵

    cout << "/* 用meanStdDev同时求取图像的均值和标准差 */" << endl;
    Scalar myMean;
    myMean = mean(imgs);
    cout << "imgs均值=" << myMean << endl;
    cout << "imgs第一个通道的均值=" << myMean[0] << "    "
        << "imgs第二个通道的均值=" << myMean[1] << endl << endl;

    cout << "/* 用meanStdDev同时求取图像的均值和标准差 */" << endl;
    Mat myMeanMat, myStddevMat;

    meanStdDev(img, myMeanMat, myStddevMat);
    cout << "img均值=" << myMeanMat << "    " << endl;
    cout << "img标准差=" << myStddevMat << endl << endl;
    meanStdDev(imgs, myMeanMat, myStddevMat);
    cout << "imgs均值=" << myMeanMat << "    " << endl << endl;
    cout << "imgs标准差=" << myStddevMat << endl;
    return 0;
}

3.2.2 两图像间的像素操作

  • 比较运算:max()函数和min()函数

src1:第一个图像矩阵

src2:第二个图像矩阵,,,和第一个图像的尺寸/通道数/数据类型要一致

dst:保留位置较大的图像矩阵

void cv::max(InputArray src1,
                InputArray src2,
                OutputArray dst)

示例:

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace std;
using namespace cv;

int main()
{
    float a[12] = { 1, 2, 3.3, 4, 5, 9, 5, 7, 8.2, 9, 10, 2 };
    float b[12] = { 1, 2.2, 3, 1, 3, 10, 6, 7, 8, 9.3, 10, 1 };
    Mat imga = Mat(3, 4, CV_32FC1, a);
    Mat imgb = Mat(3, 4, CV_32FC1, b);
    Mat imgas = Mat(2, 3, CV_32FC2, a);
    Mat imgbs = Mat(2, 3, CV_32FC2, b);

    //对两个单通道矩阵进行比较运算
    Mat myMax, myMin;
    max(imga, imgb, myMax);
    min(imga, imgb, myMin);

    //对两个多通道矩阵进行比较运算
    Mat myMaxs, myMins;
    max(imgas, imgbs, myMaxs);
    min(imgas, imgbs, myMins);

    //对两张彩色图像进行比较运算
    Mat img0 = imread("len.png");
    Mat img1 = imread("noobcv.jpg");

    if (img0.empty() || img1.empty())
    {
        cout << "请确认图像文件名称是否正确" << endl;
        return -1;
    }
    Mat comMin, comMax;
    max(img0, img1, comMax);
    min(img0, img1, comMin);
    imshow("comMin", comMin);
    imshow("comMax", comMax);

    //与掩模进行比较运算
    Mat src1 = Mat::zeros(Size(512, 512), CV_8UC3);
    Rect rect(100, 100, 300, 300);
    src1(rect) = Scalar(255, 255, 255);  //生成一个低通300*300的掩模
    Mat comsrc1, comsrc2;
    min(img0, src1, comsrc1);
    imshow("comsrc1", comsrc1);

    Mat src2 = Mat(512, 512, CV_8UC3, Scalar(0, 0, 255));  //生成一个显示红色通道的低通掩模
    min(img0, src2, comsrc2);
    imshow("comsrc2", comsrc2);

    //对两张灰度图像进行比较运算
    Mat img0G, img1G, comMinG, comMaxG;
    cvtColor(img0, img0G, COLOR_BGR2GRAY);
    cvtColor(img1, img1G, COLOR_BGR2GRAY);
    max(img0G, img1G, comMaxG);
    min(img0G, img1G, comMinG);
    imshow("comMinG", comMinG);
    imshow("comMaxG", comMaxG);
    waitKey(0);
    return 0;  }
  • 逻辑运算 :

bitwise_and()“与”

bitwise_or()“或”

bitwise_xor()“异或”

bitwise_not()“非”:只能针对一个数值进行

void cv::bitwise_and(InputArray src1,
                    InputArray src2,
                    OutputArray dst,
                    InputArray mask = noArray()
                    )

示例:

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace std;
using namespace cv;

int main()
{
    Mat img = imread("lena.png");
    if (img.empty())
    {
        cout << "请确认图像文件名称是否正确" << endl;
        return -1;
    }
    //创建两个黑白图像
    Mat img0 = Mat::zeros(200, 200, CV_8UC1);
    Mat img1 = Mat::zeros(200, 200, CV_8UC1);
    Rect rect0(50, 50, 100, 100);
    img0(rect0) = Scalar(255);
    Rect rect1(100, 100, 100, 100);
    img1(rect1) = Scalar(255);
    imshow("img0", img0);
    imshow("img1", img1);

    //进行逻辑运算
    Mat myAnd, myOr, myXor, myNot, imgNot;
    bitwise_not(img0, myNot);
    bitwise_and(img0, img1, myAnd);
    bitwise_or(img0, img1, myOr);
    bitwise_xor(img0, img1, myXor);
    bitwise_not(img, imgNot);
    imshow("myAnd", myAnd);
    imshow("myOr", myOr);
    imshow("myXor", myXor);
    imshow("myNot", myNot);
    imshow("img", img);
    imshow("imgNot", imgNot);
    waitKey(0);
    return 0;
}

3.2.3 图形二值化

定义:只有白色和黑色的图像,都只有最大值和最小值取值

函数:threshold()函数和adaptiveThreshold()函数

 double cv::threshold(InputArray src,     
                        OutputArray dst,
                        double thresh,
                        double maxval,
                        int type)
//src:待二值化图像,只能是CV_8U和CV_32F类型
//dst:输出的图像
//thresh:二值化阈值
//maxval:二值化过程最大值,只有在BINARY和INV方法中使用
//type:选择图像二值化方法的标志

double cv::threshold(InputArray src,     
                        OutputArray dst,
                        double maxValue,
                        int adaptiveMethod,
                        int thresholdType,
                        int blockSize,
                        double C)
// src:待二值化图像   dst:输出图像   maxValue:二值化的最大值  
//adaptiveMethod:自适应确定阈值方法   thresholdType:选择二值化方法的标志
//blockSize:自适应确定阈值的像素邻域大小,一般为3、5、7

示例:

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace std;
using namespace cv;

int main()
{
    Mat img = imread("lena.png");
    if (img.empty())
    {
        cout << "请确认图像文件名称是否正确" << endl;
        return -1;
    }

    Mat gray;
    cvtColor(img, gray, COLOR_BGR2GRAY);
    Mat img_B, img_B_V, gray_B, gray_B_V, gray_T, gray_T_V, gray_TRUNC;

    //彩色图像二值化
    threshold(img, img_B, 125, 255, THRESH_BINARY);
    threshold(img, img_B_V, 125, 255, THRESH_BINARY_INV);
    imshow("img_B", img_B);
    imshow("img_B_V", img_B_V);

    //灰度图BINARY二值化
    threshold(gray, gray_B, 125, 255, THRESH_BINARY);
    threshold(gray, gray_B_V, 125, 255, THRESH_BINARY_INV);
    imshow("gray_B", gray_B);
    imshow("gray_B_V", gray_B_V);

    //灰度图像TOZERO变换
    threshold(gray, gray_T, 125, 255, THRESH_TOZERO);
    threshold(gray, gray_T_V, 125, 255, THRESH_TOZERO_INV);
    imshow("gray_T", gray_T);
    imshow("gray_T_V", gray_T_V);

    //灰度图像TRUNC变换
    threshold(gray, gray_TRUNC, 125, 255, THRESH_TRUNC);
    imshow("gray_TRUNC", gray_TRUNC);

    //灰度图像大津法和三角形法二值化
    Mat img_Thr = imread("threshold.png", IMREAD_GRAYSCALE);
    Mat img_Thr_O, img_Thr_T;
    threshold(img_Thr, img_Thr_O, 100, 255, THRESH_BINARY | THRESH_OTSU);
    threshold(img_Thr, img_Thr_T, 125, 255, THRESH_BINARY | THRESH_TRIANGLE);
    imshow("img_Thr", img_Thr);
    imshow("img_Thr_O", img_Thr_O);
    imshow("img_Thr_T", img_Thr_T);

    //灰度图像自适应二值化
    Mat adaptive_mean, adaptive_gauss;
    adaptiveThreshold(img_Thr, adaptive_mean, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 55, 0);
    adaptiveThreshold(img_Thr, adaptive_gauss, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 55, 0);

    imshow("adaptive_mean", adaptive_mean);
    imshow("adaptive_gauss", adaptive_gauss);
    waitKey(0);
    return 0;
}

3.2.4 LUT 显示查找表(像素灰度值映射表)

void cv::LUT(src,lut,dst)
//src:输入图像矩阵,类型只能是cv_8u
//LUT:256个像素灰度值的查找表     dst:输出图像矩阵,数据类型和lut相同

3.3 图像变换

3.3.1 图像连接

定义:将高度或宽度相同的图形连接在一起

  • 上下连接:vconcat()函数
void cv::vconcat(const *  src,size_t nsrc,dst)
//src:mat类型的数组(具有相同的数据类型和通道数),,,nsrc数组中mat类型的数目,,,dst连接后的mat矩阵
void cv::vconcat(src1,src2,dst)
//src1第一个需要连接的mat矩阵,,,dst输出矩阵
  • 左右连接:hconcat()函数

原型同上

示例:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

int main()
{
    //矩阵数组的横竖连接
    Mat matArray[] = { Mat(1, 2, CV_32FC1, cv::Scalar(1)),
        Mat(1, 2, CV_32FC1, cv::Scalar(2)) };
    Mat vout, hout;
    vconcat(matArray, 2, vout);
    cout << "图像数组竖向连接:" << endl << vout << endl;
    hconcat(matArray, 2, hout);
    cout << "图像数组横向连接:" << endl << hout << endl;

    //矩阵的横竖拼接
    Mat A = (cv::Mat_<float>(2, 2) << 1, 7, 2, 8);
    Mat B = (cv::Mat_<float>(2, 2) << 4, 10, 5, 11);
    Mat vC, hC;
    vconcat(A, B, vC);
    cout << "多个图像竖向连接:" << endl << vC << endl;
    hconcat(A, B, hC);
    cout << "多个图像横向连接:" << endl << hC << endl;

    //读取4个子图像,00表示左上角、01表示右上角、10表示左下角、11表示右下角
    Mat img00 = imread("lena00.png");
    Mat img01 = imread("lena01.png");
    Mat img10 = imread("lena10.png");
    Mat img11 = imread("lena11.png");
    if (img00.empty() || img01.empty() || img10.empty() || img11.empty())
    {
        cout << "请确认图像文件名称是否正确" << endl;
        return -1;
    }
    //显示4个子图像
    imshow("img00", img00);
    imshow("img01", img01);
    imshow("img10", img10);
    imshow("img11", img11);

    //图像连接
    Mat img, img0, img1;
    //图像横向连接
    hconcat(img00, img01, img0);
    hconcat(img10, img11, img1);
    //横向连接结果再进行竖向连接
    vconcat(img0, img1, img);

    //显示连接图像的结果
    imshow("img0", img0);
    imshow("img1", img1);
    imshow("img", img);
    waitKey(0);
    return 0;
}

3.3.2 图像尺寸变化 resize()函数

定义:改变图像的长和宽,实现图像的缩放

void cv::resize(src,dst,Size dsize,double fx=0,double fy=0,
                int interpolation=INTER_LINEAR)
//src:输入,,,dst:输出,,,dsize:输出图像的尺寸,,,
//fx:水平轴的比例因子,,,fy:垂直轴的比例因子,,,
//interpolation:插值方法的标志,表3-3

示例:

#include <opencv2\opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;

int main()
{
    Mat gray = imread("lena.png", IMREAD_GRAYSCALE);
    if (gray.empty())
    {
        cout << "请确认图像文件名称是否正确" << endl;
        return -1;
    }

    Mat smallImg, bigImg0, bigImg1, bigImg2;
    resize(gray, smallImg, Size(15, 15), 0, 0, INTER_AREA);  //先将图像缩小
    resize(smallImg, bigImg0, Size(30, 30), 0, 0, INTER_NEAREST);  //最近邻差值
    resize(smallImg, bigImg1, Size(30, 30), 0, 0, INTER_LINEAR);  //双线性差值
    resize(smallImg, bigImg2, Size(30, 30), 0, 0, INTER_CUBIC);  //双三次差值
    namedWindow("smallImg", WINDOW_NORMAL);  //图像尺寸太小,一定要设置可以调节窗口大小标志
    imshow("smallImg", smallImg);
    namedWindow("bigImg0", WINDOW_NORMAL);
    imshow("bigImg0", bigImg0);
    namedWindow("bigImg1", WINDOW_NORMAL);
    imshow("bigImg1", bigImg1);
    namedWindow("bigImg2", WINDOW_NORMAL);
    imshow("bigImg2", bigImg2);
    waitKey(0);
    return 0;
}

3.3.3 图像翻转变换flip()函数

void cv::flip(src,dst,int flipCode)
//flipCode:翻转方式标志。数值大于0,绕y轴旋转;数值等于0,绕x轴旋转;数值小于0,绕两轴旋转

示例:

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;

int main()
{
    Mat img = imread("lena.png");
    if (img.empty())
    {
        cout << "请确认图像文件名称是否正确" << endl;
        return -1;
    }

    Mat img_x, img_y, img_xy;
    flip(img, img_x, 0);  //沿x轴对称
    flip(img, img_y, 1);  //沿y轴对称
    flip(img, img_xy, -1);  //先x轴对称,再y轴对称
    imshow("img", img);
    imshow("img_x", img_x);
    imshow("img_y", img_y);
    imshow("img_xy", img_xy);
    waitKey(0);
    return 0;
}

3.3.4 图像仿射变换==图像旋转/平移/缩放

数学表示:先×一个线性变换矩阵再加上一个平移向量,其中矩阵变换为2*2矩阵,平移向量为2*1向量

  • getRotationMatrix2D()函数:计算旋转矩阵
Mat cv::getRotationMatrix2D(Pont2f center,double angle,double scale)
//center:旋转的中心位置,,,angle:图像的旋转角度,单位为度,正值为逆矩阵旋转
//scale:俩个轴的比例因子,可以实现旋转过程中的缩放,不缩放则输入1,如式子3-11所示

  • warpAffine()函数:实现图像的仿射变换
void cv::warpAffine(src,dst,M,dsize,int flags= INTER_LINEAR,
int borderMode=BORDER_COSTANT,const Scalar& borderValue=Scalar())
//M:2*3的变换矩阵,,,dsize:输出图像的尺寸,,,flags:插值方法的标志,,,表3-4
//bordedMode:像素边界外推方法的标志,,,表3-5 
//borderValue:填充边界使用的数值,默认情况是0

  • getAffineTransform()函数
Mat cv::getAffineTransform(src[],dst[])
//src[]:源图像的3个像素坐标,,,dst[]目标图像的3个像素坐标

示例:

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace std;
using namespace cv;

int main()
{
    Mat img = imread("lena.png");
    if (img.empty())
    {
        cout << "请确认图像文件名称是否正确" << endl;
        return -1;
    }

    Mat rotation0, rotation1, img_warp0, img_warp1;
    double angle = 30;  //设置图像旋转的角度
    Size dst_size(img.rows, img.cols);  //设置输出图像的尺寸
    Point2f center(img.rows / 2.0, img.cols / 2.0);  //设置图像的旋转中心
    rotation0 = getRotationMatrix2D(center, angle, 1);  //计算放射变换矩阵
    warpAffine(img, img_warp0, rotation0, dst_size);  //进行仿射变换
    imshow("img_warp0", img_warp0);
    //根据定义的三个点进行仿射变换
    Point2f src_points[3];
    Point2f dst_points[3];
    src_points[0] = Point2f(0, 0);  //原始图像中的三个点
    src_points[1] = Point2f(0, (float)(img.cols - 1));
    src_points[2] = Point2f((float)(img.rows - 1), (float)(img.cols - 1));
    dst_points[0] = Point2f((float)(img.rows)*0.11, (float)(img.cols)*0.20);  //放射变换后图像中的三个点
    dst_points[1] = Point2f((float)(img.rows)*0.15, (float)(img.cols)*0.70);
    dst_points[2] = Point2f((float)(img.rows)*0.81, (float)(img.cols)*0.85);
    rotation1 = getAffineTransform(src_points, dst_points);  //根据对应点求取仿射变换矩阵
    warpAffine(img, img_warp1, rotation1, dst_size);  //进行仿射变换
    imshow("img_warp1", img_warp1);
    waitKey(0);
    return 0;
}

3.3.5 图像透视变换

定义:按照物体成像投影规律进行变换,将物体重新投影到新的成像平面

  • getPerspectiveTransform()函数
Mat cv::getPerspectiveTransform(src[],dst[],int solveMethod= DECOMP_LU)
//solveMethod:选择计算透视变换矩阵方法的标志,默认是DECOMP_LU最佳主轴元素的高斯消元法
  • warpPerspective()函数

原型与上文3.3.4的warpAffine()函数相同

示例:

#include <opencv2\opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;

int main()
{
    Mat img = imread("noobcvqr.png");
    if (img.empty())
    {
        cout << "请确认图像文件名称是否正确" << endl;
        return -1;
    }

    Point2f src_points[4];
    Point2f dst_points[4];
    //通过Image Watch查看的二维码四个角点坐标
    src_points[0] = Point2f(94.0, 374.0);
    src_points[1] = Point2f(507.0, 380.0);
    src_points[2] = Point2f(1.0, 623.0);
    src_points[3] = Point2f(627.0, 627.0);
    //期望透视变换后二维码四个角点的坐标
    dst_points[0] = Point2f(0.0, 0.0);
    dst_points[1] = Point2f(627.0, 0.0);
    dst_points[2] = Point2f(0.0, 627.0);
    dst_points[3] = Point2f(627.0, 627.0);
    Mat rotation, img_warp;
    rotation = getPerspectiveTransform(src_points, dst_points);  //计算透视变换矩阵
    warpPerspective(img, img_warp, rotation, img.size());  //透视变换投影
    imshow("img", img);
    imshow("img_warp", img_warp);
    waitKey(0);
    return 0;
}

3.3.6 极坐标变换 warpPolar()函数

void cv::warpPolar(src,dst,目标图像大小,极坐标的原点坐标,
                        半径,插值映射方法(通过“|”和“+”连接))

可进行正变换也可逆变换,取决于最后一个参数:

极坐标变换:WARP_POLAR_LINEAR

半对数极坐标变换:WARP_POLAR_LOG

逆变换:WARP_INVERSE_MAP

示例:

#include <opencv2\opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;

int main()
{
    Mat img = imread("dial.png");
    if (!img.data)
    {
        cout << "请检查图像文件名称是否输入正确" << endl;
        return -1;
    }

    Mat img1, img2;
    Point2f center = Point2f(img.cols / 2, img.rows/2);  //极坐标在图像中的原点
    //正极坐标变换,INTER_LINEAR双线性插值
    warpPolar(img, img1, Size(300,600), center, center.x, 
            INTER_LINEAR + WARP_POLAR_LINEAR);
    //逆极坐标变换
    warpPolar(img1, img2, Size(img.rows,img.cols), center, center.x, 
                INTER_LINEAR + WARP_POLAR_LINEAR + WARP_INVERSE_MAP);

    imshow("原表盘图", img);
    imshow("表盘极坐标变换结果", img1); 
    imshow("逆变换结果", img2);
    waitKey(0);
    return 0;
}

3.4 在图像上绘制几何图形

3.4.1 绘制图形 circle()函数

void cv::circle(需要绘制的图像,圆心位置坐标,圆的半径,
                圆形的颜色,轮廓的宽度(负值表示实心圆),
                边界的类型(FILLED/LINE_4/LINE_8/LINE_AA),小数位数)

3.4.2 绘制直线 line()函数

void cv::line(需要绘制的图像,直线起点坐标,直线终点坐标,
                颜色,宽度,边界的类型(FILLED/LINE_4/LINE_8/LINE_AA),小数位数)

3.4.3 绘制椭圆 ellipse()函数或ellipse2Poly()函数

void cv::ellipse(需要绘制的图像,椭圆的中心坐标,椭圆主轴大小的一半,
                椭圆旋转角度,椭圆弧起始的角度,终止的角度,颜色,轮廓的宽度(负值表示实心),
                边界的类型(FILLED/LINE_4/LINE_8/LINE_AA),小数位数)

3.4.4 绘制多边形fillPoly()函数和rectangle()函数

3.4.5 文字生成 putText()函数

fontFace字体类型及含义

示例:

#include <opencv2\opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;

int main()
{
    Mat img = Mat::zeros(Size(512, 512), CV_8UC3);  //生成一个黑色图像用于绘制几何图形
    //绘制圆形
    circle(img, Point(50, 50), 25, Scalar(255, 255, 255), -1);  //绘制一个实心圆
    circle(img, Point(100, 50), 20, Scalar(255, 255, 255), 4);  //绘制一个空心圆
    //绘制直线
    line(img, Point(100, 100), Point(200, 100), Scalar(255, 255, 255), 2, LINE_4,0);  //绘制一条直线
    //绘制椭圆
    ellipse(img, Point(300, 255), Size(100, 70), 0, 0, 100, Scalar(255, 255, 255), -1);  //绘制实心椭圆的一部分
    ellipse(img, RotatedRect(Point2f(150, 100), Size2f(30, 20), 0), Scalar(0, 0, 255), 2);  //绘制一个空心椭圆
    vector<Point> points;
    ellipse2Poly(Point(200, 400), Size(100, 70),0,0,360,2,points);  //用一些点来近似一个椭圆
    for (int i = 0; i < points.size()-1; i++)  //用直线把这个椭圆画出来
    {
        if (i==points.size()-1)
        {
            line(img, points[0], points[i], Scalar(255, 255, 255), 2);  //椭圆中后于一个点与第一个点连线
            break;
        }
        line(img, points[i], points[i+1], Scalar(255, 255, 255), 2);  //当前点与后一个点连线
    }
    //绘制矩形
    rectangle(img, Point(50, 400), Point(100, 450), Scalar(125, 125, 125), -1);  
    rectangle(img, Rect(400,450,60,50), Scalar(0, 125, 125), 2);
    //绘制多边形
    Point pp[2][6];
    pp[0][0] = Point(72, 200);
    pp[0][1] = Point(142, 204);
    pp[0][2] = Point(226, 263);
    pp[0][3] = Point(172, 310);
    pp[0][4] = Point(117, 319);
    pp[0][5] = Point(15, 260);
    pp[1][0] = Point(359, 339);
    pp[1][1] = Point(447, 351);
    pp[1][2] = Point(504, 349);
    pp[1][3] = Point(484, 433);
    pp[1][4] = Point(418, 449);
    pp[1][5] = Point(354, 402);
    Point pp2[5];
    pp2[0] = Point(350, 83);
    pp2[1] = Point(463, 90);
    pp2[2] = Point(500, 171);
    pp2[3] = Point(421, 194);
    pp2[4] = Point(338, 141);
    const Point* pts[3] = { pp[0],pp[1],pp2 };  //pts变量的生成
    int npts[] = { 6,6,5 };  //顶点个数数组的生成
    fillPoly(img, pts, npts, 3, Scalar(125, 125, 125),8);  //绘制3个多边形
    //生成文字
    putText(img, "Learn OpenCV 4",Point(100, 400), 2, 1, Scalar(255, 255, 255));
    imshow("", img);
    waitKey(0);
    return 0;
}

3.5 感兴趣区域==截取部分图像 ROI方法

  • 截取部分在原图的位置标出来 Rect函数和range函数
  • copyTo()函数进行深拷贝

示例:

#include <opencv2\opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;

int main()
{
    Mat img = imread("lena.png");
    Mat noobcv = imread("noobcv.jpg");
    if (img.empty() || noobcv.empty())
    {
        cout << "请确认图像文件名称是否正确" << endl;
        return -1;
    }
    Mat ROI1, ROI2, ROI2_copy, mask, img2, img_copy, img_copy2;
    resize(noobcv, mask, Size(200, 200));
    img2 = img;  //浅拷贝
                 //深拷贝的两种方式
    img.copyTo(img_copy2);
    copyTo(img, img_copy, img);
    //两种在图中截取ROI区域的方式
    Rect rect(206, 206, 200, 200);  //定义ROI区域
    ROI1 = img(rect);  //截图
    ROI2 = img(Range(300, 500), Range(300, 500));  //第二种截图方式
    img(Range(300, 500), Range(300, 500)).copyTo(ROI2_copy);  //深拷贝
    mask.copyTo(ROI1);  //在图像中加入部分图像
    imshow("加入noobcv后图像", img);
    imshow("ROI对ROI2的影响", ROI2);
    imshow("深拷贝的ROI2_copy", ROI2_copy);
    circle(img, Point(300, 300), 20, Scalar(0, 0, 255), -1);  //绘制一个圆形
    imshow("浅拷贝的img2", img2);
    imshow("深拷贝的img_copy", img_copy);
    imshow("深拷贝的img_copy2", img_copy2);
    imshow("画圆对ROI1的影响", ROI1);
    waitKey(0);
    return 0;
}

3.6 图像金字塔

3.6.1 高斯金字塔 pyrDown()函数

是解决尺度不确定性的方法,通过下采样不断将图像尺寸缩小,进而使图像包含多种尺寸,一般每层缩小为原来的一半,一般 3-6层

void cv::pyrDowm(src,dst,size,borderType(像素边界外推方法的标志,见表3-5))

size要满足方程式和计算:

3.6.2 拉普拉斯金字塔 pyrUp()函数

相反,通过(上采样)上层小尺寸构建下层大尺寸的图形,具有预测残差的作用

示例:

#include <opencv2\opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;

int main()
{
    Mat img = imread("lena.png");
    if (img.empty())
    {
        cout << "请确认图像文件名称是否正确" << endl;
        return -1;
    }

    vector<Mat> Gauss, Lap;  //高斯金字塔和拉普拉斯金字塔
    int level = 3;  //高斯金字塔下采样次数
    Gauss.push_back(img);  //将原图作为高斯金字塔的第0层
                           //构建高斯金字塔
    for (int i = 0; i < level; i++)
    {
        Mat gauss;
        pyrDown(Gauss[i], gauss);  //下采样
        Gauss.push_back(gauss);
    }
    //构建拉普拉斯金字塔
    for (int i = Gauss.size() - 1; i > 0; i--)
    {
        Mat lap, upGauss;
        if (i == Gauss.size() - 1)  //如果是高斯金字塔中的最上面一层图像
        {
            Mat down;
            pyrDown(Gauss[i], down);  //上采样
            pyrUp(down, upGauss);
            lap = Gauss[i] - upGauss;
            Lap.push_back(lap);
        }
        pyrUp(Gauss[i], upGauss);
        lap = Gauss[i - 1] - upGauss;
        Lap.push_back(lap);
    }
    //查看两个金字塔中的图像
    for (int i = 0; i < Gauss.size(); i++)
    {
        string name = to_string(i);
        imshow("G" + name, Gauss[i]);
        imshow("L" + name, Lap[i]);
    }
    waitKey(0);
    return 0;
}

3.7 窗口交互操作

3.7.1 图像窗口滑动条 createTrackbar()

int cv::createTrackbar(滑动条名称,窗口名称,指向整数变量的指针,滑动条最大值,
                            滑动调用的指针,传递给回调函数的可选参数))

示例:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

//为了能在被调函数中使用,所以设置成全局的
int value;
void callBack(int, void*);  //滑动条回调函数
Mat img1, img2;

int main()
{
    img1 = imread("lena.png");
    if (!img1.data)
    {
        cout << "请确认是否输入正确的图像文件" << endl;
        return -1;
    }
    namedWindow("滑动条改变图像亮度");
    imshow("滑动条改变图像亮度", img1);
    value = 100;  //滑动条创建时的初值
                  //创建滑动条
    createTrackbar("亮度值百分比", "滑动条改变图像亮度", &value, 600, callBack, 0);
    waitKey();
}

static void callBack(int, void*)
{
    float a = value / 100.0;
    img2 = img1 * a;
    imshow("滑动条改变图像亮度", img2);
}

3.7.2 鼠标响应 setMouseCallback()函数

void cv::setMouseCallback(添加鼠标响应窗口的名字,回调函数,传递给回调函数的参数,一般为0)
  • 回调函数MouseCallback()函数
typedef void(* cv::MouseCallback)(int event,int x,int y,int flags,void *userdata)
//event:鼠标响应事件的标志    x:鼠标指针在图像坐标系中的x坐标
//flags:鼠标响应的标志  usedata:传递给回调函数的参数 

示例:

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;

Mat img, imgPoint; //全局的图像
Point prePoint; //前一时刻鼠标的坐标,用于绘制直线
void mouse(int event, int x, int y, int flags, void*);

int main()
{
    img = imread("lena.png");
    if (!img.data)
        {
            cout << "请确认输入图像名称是否正确! " << endl;
            return -1;
        }
    img.copyTo(imgPoint);
    imshow("图像窗口 1", img);
    imshow("图像窗口 2", imgPoint);
    setMouseCallback("图像窗口 1", mouse, 0); //鼠标影响
    waitKey(0);
    return 0;
    }

void mouse(int event, int x, int y, int flags, void*)
{
    if (event == EVENT_RBUTTONDOWN) //单击右键
    {
        cout << "点击鼠标左键才可以绘制轨迹" << endl;
    }
    if (event == EVENT_LBUTTONDOWN) //单击左键,输出坐标
    {
        prePoint = Point(x, y);
        cout << "轨迹起始坐标" << prePoint << endl;
    }
    if (event == EVENT_MOUSEMOVE && (flags & EVENT_FLAG_LBUTTON)) //鼠标按住左键移动第 3 章 图像基本操作
    {
     //通过改变图像像素显示鼠标移动轨迹
        imgPoint.at<Vec3b>(y, x) = Vec3b(0, 0, 255);
        imgPoint.at<Vec3b>(y, x - 1) = Vec3b(0, 0, 255);
        imgPoint.at<Vec3b>(y, x + 1) = Vec3b(0, 0, 255);
        imgPoint.at<Vec3b>(y + 1, x) = Vec3b(0, 0, 255);
        imgPoint.at<Vec3b>(y + 1, x) = Vec3b(0, 0, 255);
        imshow("图像窗口 2", imgPoint);
        
        //通过绘制直线显示鼠标移动轨迹
        Point pt(x, y);
        line(img, prePoint, pt, Scalar(0, 0, 255), 2, 5, 0);
        prePoint = pt;
        imshow("图像窗口 1", img);
    }
}

3.8总结

至此,第三章就结束了,望各位读者“一键三连”,再次感谢读到这里的各位读者。有任何问题欢迎评论区留言或者私信。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐