OpenCV4 快速入门 学习笔记分享【第三章】
本人正在学习 OpenCV 计算机视觉相关知识,此篇为个人学习笔记,参考书籍为冯振等人的《OpenCV4 快速入门》,主要记录 OpenCV 在 Linux 环境下的编译、运行实操流程与相关知识点总结。
笔记内容均为亲自实操验证,过程中踩过不少编译配置、运行参数的坑,整理出来一方面用于自己复盘巩固,另一方面也希望能帮助到刚入门 OpenCV、在编译运行示例程序时遇到问题的小伙伴。文中内容如有疏漏或错误之处,欢迎各位大佬评论区指正交流,共同进步~
第三章 图像基本操作
3.1 图像颜色空间
3.1.1 颜色模型与转换
- RGB颜色模型+A模型(A模型表示透明度)
opencv:BGR(蓝绿红)某个通道都表示颜色0-1的过程
白色:0,0,0 黑色:255,255,255
- 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
- HSV颜色模型
锥形结构。H:色度(平时说的颜色) S:饱和度(颜色的纯度,越高越艳,,,范围0-100%) V:亮度(颜色的明亮程度,,,范围0-max)
- Lab颜色模型
球形结构。L是亮度,a\b是两个颜色通道,,,范围-128~127; a通道从小到大是绿到红,b通道小到大是蓝到黄
- GRAY颜色模型
灰度模型。0~max表示黑到白,255表示白色
RGB转换GRAY:GRAY=0.3R+0.59G+0.11B
- 不同颜色模型转换
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总结


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