OpenCV数字图像处理基于C++:基本操作
1、图像的读取、显示和保存示例
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace cv;
using namespace std;
int main()
{
Mat image; //创建一个空图像image
image = imread("E://2.png"); //读取文件夹中的图像
//检测图像是否加载成功
if (image.empty()) //检测image有无数据,无数据 image.empty()返回 真
{
cout << "Could not open or find the image" << endl;
return -1;
}
namedWindow("IMAGE"); //创建显示窗口,不加这行代码,也能显示,默认窗口大小不能改变
imshow("IMAGE", image); //在窗口显示图像
imwrite("D:\\1.png", image); //保存图像为png格式,文件名称为1
waitKey(0); //暂停,保持图像显示,等待按键结束
return 0;
}
1.1 imread函数
使用imread()读取图像,imread包含两个参数:imread(图像路径, 图像形式);
其中图像形式有三种:
1.加载彩色图像(默认加载形式)
imread(图像路径, IMREAD_COLOR);或者:imread(图像路径, 1);
2.加载灰度模式图像
imread(图像路径, IMREAD_GRAYSCALE);或者:imread(图像路径, 0);
3.加载图像,包括alpha通道
imread(图像路径,IMREAD_UNCHANGED);或者:imread(图像路径, -1);
1.2 namedWindow函数
功能:namedWindow() 的功能就是新建一个显示窗口,用来显示图像。
namedWindow() 包含两个参数:namedWindow(窗口名称, 窗口形式)
窗口形式常用的两种:
1.显示的图像大小不能改变(默认形式)
namedWindow(窗口名称, WINDOW_AUTOSIZE)
2.图像大小能够调节
namedWindow(窗口名称, WINDOW_NORMAL)
1.3 imshow函数
功能:imshow函数用于显示图像。
imshow() 函数包含两个参数:
imshow(窗口名称,图像名称)
1.4 imwrite函数
功能:imwrite函数用于显示图像。
imwrite() 函数包含两个参数:
imwrite(保存图像名称及格式,图像名称)
2、Mat创建图像(矩阵),获取图像信息,感兴趣区域(Rect)
2.1 创建图像(矩阵):Mat
使用Mat创建图像(矩阵)的常用形式有:
1.创建一个空图像,大小为0
Mat image1;
2.指定矩阵大小,指定数据类型:
Mat image1(100,100,CV_8U);
这里包含三个参数:矩阵行数,矩阵列数,数据类型;
其中数据类型有很多种,常用的应该有:
CV_8U:8位无符号型(0~255),即灰度图像;
CV_8UC3:三通道8位无符号型,这里三通道指B(蓝)G(绿)R(红),与matlab中的RGB正好相反。
这里创建矩阵时未指定矩阵的值,发现默认值的大小为205.
3.指定矩阵大小,指定数据类型,设置初始值:
Mat image1(100,100,CV_8U, 100);
这里包含四个参数:矩阵行数,矩阵列数,数据类型,初始值;
对于灰度图像:可以直接给出初始值,也可以使用Scalar();
Mat image1(100,100,CV_8U, 100);
Mat image1(100,100,CV_8U, Scalar(100));
对于三通道图像:使用Scalar();
Mat image1(100,100,CV_8UC3, Scalar(100,100,100));
2.2 获取图像信息
获取图像的宽度(列数),高度(行数),尺寸和通道数:
#include<iostream>
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main() {
Mat image = imread("E:\\2.png",0);
if (image.empty()) {
cout << "读取错误!" << endl;
return -1;
}
namedWindow("Display window");
imshow("Display window", image); // 显示图像
cout << "图像的行数为: " << image.rows<<endl; // 获取图像的高度(行数)
cout << "图像的列数为: " << image.cols<<endl; // 获取图像的宽度(列数)
cout << "图像的通道数为: " << image.channels() << endl; // 获取图像的通道数(彩色 3; 灰度 1)
cout << "图像的尺寸为: " << image.size<<endl; // 获取图像的尺寸(行*列)
waitKey(0);
return 0;
}
2.3 感兴趣区域
通过Rect()定义一个感兴趣区域:
#include<iostream>
#include<opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat image1 = imread("E:\\2.png"); //读取图像;
if (image1.empty())
{
cout << "读取错误" << endl;
return -1;
}
imshow("image1", image1); //显示图像;
Mat imageROI(image1, Rect(50, 50, 80, 80)); //定义感兴趣区域
imshow("image2", imageROI);
waitKey(0); //暂停,保持图像显示,等待按键结束
return 0;
}
其中Rect()有四个参数,Rect(a,b,c,d):
a:感兴趣区域列(cols)的起点;
b:感兴趣区域行(rows)的起点;
c:感兴趣区域的列数(cols);
d:感兴趣区域的行数(rows);
3、 单窗口显示多幅图像与贴图
//Mat每个格子内的数据格式---------- - Mat定义
//Mat_<uchar>-------- - CV_8U(0-255)
//Mat<char>---------- - CV_8S(-128-127)
//Nat_<short>-------- - CV_16S(-32768-32767)
//Mat_<ushort>--------CV_16U(0-65535)
//Mat_<int>---------- - CV_32S(-2147483648-2147483647)
//Mat_<float>----------CV_32F(-FLT_MAX…FLT_MAX,INF,NAN)
//Mat_<double>--------CV_64F(-DBL_MAX…DBL_MAX,INF,NAN)
//Mat数据类型和通道对应的type()
// C1 C2 C3 C4
//CV_8U 0 8 16 24
//CV_8S 1 9 17 25
//CV_16U 2 10 18 26
//CV_16S 3 11 19 27
//CV_32S 4 12 20 28
//CV_32F 5 13 21 29
//CV_64F 6 14 22 30
3.1 单窗口显示多幅图片
为了实现这个效果中,在OpenCV中,可以在一幅黑色背景大图上把各幅图绘制上去,这其中关键是要计算出各幅图绘制在大图中的哪个区域,并对图像按显示区大小进行相应的缩放。
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/imgproc/types_c.h>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/highgui/highgui_c.h>
#include<iostream>
using namespace cv;
using namespace std;
/*******************同时显示多张图片*************************
*srcImages 为要显示的图片的集合;
*imageSize 为要显示的图片的大小;
*最多同时显示12张图片
*************************************************************/
void showManyImages(const vector<Mat>& srcImages, Size imageSize) {
int nNumImages = srcImages.size(); //获取图片数量
Size nSizeWindows;
if (nNumImages > 12) {
cout << "no more tha 12 images" << endl;
return;
}
//根据图片序列数量来确定分割小窗口的形态
switch (nNumImages) {
case 1:nSizeWindows = Size(1, 1); break;
case 2:nSizeWindows = Size(2, 1); break;
case 3:
case 4:nSizeWindows = Size(2, 2); break;
case 5:
case 6:nSizeWindows = Size(3, 2); break;
case 7:
case 8:nSizeWindows = Size(4, 2); break;
case 9:nSizeWindows = Size(3, 3); break;
default:nSizeWindows = Size(4, 3);
}
//设置小图像尺寸,间隙,边界
int nShowImageSize = 200;
int nSplitLineSize = 15;
int nAroundLineSize = 50;
//创建输出图像,图像大小根据输入源来确定
const int imagesHeight = nShowImageSize * nSizeWindows.width + nAroundLineSize + (nSizeWindows.width - 1) * nSplitLineSize;
const int imagesWidth = nShowImageSize * nSizeWindows.height + nAroundLineSize + (nSizeWindows.height - 1) * nSplitLineSize;
cout << imagesWidth << " " << imagesHeight << endl;
// 大图像大小
Mat showWindowsImages(imagesWidth, imagesHeight, CV_8UC3, Scalar(0, 0, 0));
//提取对应小图像的左上角坐标x,y
int posX = (showWindowsImages.cols - (nShowImageSize * nSizeWindows.width + (nSizeWindows.width - 1) * nSplitLineSize)) / 2;
int posY = (showWindowsImages.rows - (nShowImageSize * nSizeWindows.height + (nSizeWindows.height - 1) * nSplitLineSize)) / 2;
cout << posX << " " << posY << endl;
int tempPosX = posX;
int tempPosY = posY;
//将每一幅小图像整合成一幅大图像
for (int i = 0; i < nNumImages; i++) {
//小图像坐标转换
if ((i % nSizeWindows.width == 0) && (tempPosX != posX)) {
tempPosX = posX;
tempPosY += (nSplitLineSize + nShowImageSize);
}
//利用Rect区域将小图像置于大图像的相应区域
//tempPosX,tempPosY图像坐标起点;nShowImageSize, nShowImageSize图像尺寸大小
Mat tempImage = showWindowsImages(Rect(tempPosX, tempPosY, nShowImageSize, nShowImageSize));
cout << tempPosX << " " << tempPosY <<" "<<nShowImageSize<< endl;
resize(srcImages[i], tempImage, Size(nShowImageSize, nShowImageSize));//利用resize函数实现图像缩放
tempPosX += (nSplitLineSize + nShowImageSize);
}//for
imshow("单窗口显示多图片", showWindowsImages);
}
int main() {
//图像源输入
vector<Mat>srcImage(9);
srcImage[0] = imread("E:\\1.png");
srcImage[1] = imread("E:\\1.png");
srcImage[2] = imread("E:\\1.png");
srcImage[3] = imread("E:\\1.png");
srcImage[4] = imread("E:\\1.png");
srcImage[5] = imread("E:\\2.png");
srcImage[6] = imread("E:\\2.png");
srcImage[7] = imread("E:\\2.png");
srcImage[8] = imread("E:\\2.png");
//判断当前vector读入的正确性
for (int i = 0; i < srcImage.size(); i++) {
if (srcImage[i].empty()) {
cout << "read error" << endl;
return -1;
}
}
//调用 单窗口显示图像
showManyImages(srcImage, Size(147, 118));
waitKey(0);
system("pause");
return 0;
}
resize(输入图像, 输出图像, 图像尺寸大小);
3.2 利用copyTo 实现图1贴在图2上
浅拷贝
浅拷贝一般有两种形式:1. 用“=” 2. 用“()”
我们知道变量名实际上是内存地址的指向,通过“=”复制图像,新Mat变量仍指向原图像地址。
浅拷贝对复制后的图像的任何操作实际都是对原图像的操作,所以名浅拷贝,并没有开辟一块空间来创建新的图像。
Mat m2 = m1;
Mat m3(m1);
m2和m3的改变会影响到m1。
深拷贝
深拷贝在opencv C++中有两种操作:1. copyTo() 2. clone()
深拷贝在内存中新开辟空间来存放复制后的图像,对复制后的图像的任何操作不会再影响原图像。
主要是注意.copyTo()和clone()的用法,两者用法有些不同,copyTo()是先声明一个mat名,原图像.copyTo到(新图像)。
int main()
{
Mat img1 = imread("E:\\1.png");
Mat img2 = imread("E:\\2.png");
/*imshow("img1", img1);
imshow("img2", img2);*/
// 在img1上创建一个感兴趣的区域
Mat imgdst = img1(Rect(0, 0, img2.cols, img2.rows));
加载图2,转换为灰度图后存入mask
//Mat mask = imread("E:\\2.png", 0);
/*imshow("img3", mask);*/
img2.copyTo(imgdst);
imshow("贴图",img1);
waitKey(0);
return 0;
}
4、窗口销毁与调整窗口大小
4.1 窗口销毁
int main() {
//图像源输入
vector<Mat>srcImage(2);
char szName[50] = "";
for (int i = 0; i < srcImage.size(); i++)
{
sprintf_s(szName, "E:\\%d.png", i + 1);
srcImage[i] = imread(szName);
if (srcImage[i].empty()) { //判断当前vector读入的正确性
cout << "read " << szName << " error" << endl;
return -1;
}
//调用 单窗口显示图像
namedWindow(szName, WINDOW_AUTOSIZE);
imshow(szName, srcImage[i]);//在“窗口1”这个窗口输出图片。
waitKey(5000);//等待5秒,程序自动退出。改为0,不自动退出。
destroyWindow(szName);
}
//destroyAllWindows();
cout << "所有的窗口已经销毁了" << endl;
waitKey(0);
system("pause");
return 0;
}
sprintf_s 函数功能:把格式化的指定长度的数据写入某个字符串。
4.2 调整窗口大小
int main() {
vector<Mat>srcImage(1);
char szName[50] = "";
int width = 640, height = 480;
sprintf_s(szName, "E:\\%d.png", 1);
srcImage[0] = imread(szName);
if (srcImage[0].empty()) {
cout << "read " << szName << " error" << endl;
return -1;
}
cout << szName;
namedWindow("img", WINDOW_NORMAL); //新建窗口
imshow("img", srcImage[0]); //在窗口显示图片
resizeWindow("img", width, height); //调整窗口大小
waitKey(0);
system("pause");
return 0;
}
5、鼠标事件与键盘事件
鼠标事件event主要有以下几种:
enum{
EVENT_MOUSEMOVE =0, //滑动
EVENT_LBUFFONDOWN =1, //左键单击
EVENT_RBUFFONDOWN =2, //右键单击
EVENT_MBUFFONDOWN =3, //中键单击
EVENT_LBUFFONUP =4, //左键放开
EVENT_RBUFFONUP =5, //右键放开
EVENT_MBUFFONUP =6, //中键放开
EVENT_LBUFFONDBLCLK =7, //左键双击
EVENT_RBUFFONDBLCLK =8, //右键双击
EVENT_MBUFFONDBLCLK =9 //中键双击
};
鼠标事件标志flags主要有以下几种:
enum{
EVENT_FLAG_LBUTTON =1, //左键拖拽
EVENT_FLAG_RBUTTON =2, //左键拖拽
EVENT_FLAG_MBUTTON =4, //左键拖拽
EVENT_FLAG_CTRLKEY =8, //按Ctrl
EVENT_FLAG_SHIFTKEY =16, //按Shift
EVENT_FLAG_ALTKEY =32, //按Alt
};
5.1 在图片上画线
void on_mouse(int event, int x, int y, int flags, void*)
{
if (event == EVENT_LBUTTONDOWN)
{
previousPoint = Point(x, y);
}
else if (event == EVENT_MOUSEMOVE && (flags & EVENT_FLAG_LBUTTON))
{
Point pt(x, y);
line(g_srcImage, previousPoint, pt, Scalar(0, 0, 255), 2, 5, 0); //画线
previousPoint = pt;
imshow("WINDOW", g_srcImage);
}
}
int main() {
g_srcImage = imread("E:\\2.png");
imshow("WINDOW", g_srcImage);
setMouseCallback("WINDOW", on_mouse, 0);
waitKey(0);
system("pause");
return 0;
}
on_mouse(int event, int x, int y, int flags, void*) 其中参数 event 表示鼠标事件,x 表示鼠标事件的 x 坐标,y 表示鼠标事件的 y 坐标,flag 表示鼠标事件的标志,userdata 是可选参数。在上述代码中,on_mouse 就是用来处理鼠标事件的回调函数,当鼠标有动作时,on_mouse 会被系统调用,然后在 on_mouse 中,判断发送了哪种动作,进行相应处理。
5.2 按Esc退出播放视频
int main()
{
VideoCapture cap("E:\\1.mp4");
if (!cap.isOpened()) {
cout << "video open error!\n";
return -1;
}
Mat frame;
while (1) {
cap >> frame; //从cap中读取数据到frame中
if (frame.empty())
break;
imshow("la",frame);
if (waitKey(30) == 27) //延时30ms,以正常的速率播放视频,播放期间按下Esc则退出播放
break;
}
return 0;
}
参考:https://zhuanlan.zhihu.com/p/486830242