图像处理:OpenCV编程详解(C++)【持续更新中】
原创不易,请勿抄袭
作者联系⽅式 : QQ:993678929
⼀. 开发环境配置
Visual Studio 2019 + opencv
这⾥仅记录配置过程中可能遇到的问题
.
由于不到 opencv_world450.dll,⽆法继续执⾏代码。重新安装程序可能会解决此问题。
到 C:\opencv\build\x64\vc15\bin ⽂件夹,将其中的opencv_videoio_ffmpeg450_64.dll opencv_videoio_msmf450_64.dll opencv_world450.dll 三个dll⽂件复制到C:\Windows\System32⽬录下(如果VS需要使⽤debug模式编译运⾏则把带d的dll也⼀同复制)
2. 在VS项⽬属性中添加包含⽬录和库⽬录以及添加链接器输⼊的附加依赖项时注意选择配置和平台
Debug和Release分别对应带d和不带d的lib⽂件(opencv_world450d.lib和opencv_world450.lib )
⽽x64和x86则应该根据你所安装的opencv版本对应的平台来选择
⼆. 图像的数字化
1.构造Mat类
#include <opencv2/core/core.hpp>
using namespace cv;
int main()
{
Mat m1 =Mat(4,2,CV_64FC(1));//构造⼀个4列2⾏(宽4 ⾼2)的double类型矩阵
Mat m2 =Mat(Size(4,2),CV_64FC(1));//这种写法也可以,同样的效果
Mat m3 =(Mat_<int>(4,2)<<1,2,3,4,5,6,7,8);//快速构造⼩型单通道矩阵(int类型)并初始化。
Mat m4;
Mat m_one = Mat::ones(4,2, CV_32FC1);//构造4列 2⾏的单通道1矩阵
Mat m_zero = Mat::zeros(4,2, CV_32FC1);//构造4列 2⾏的单通道1矩阵
return0;
}
构造m1时传⼊的三个参数,4表⽰矩阵的列数(宽度),2表⽰矩阵的⾏数(⾼度)
⽽在CV_64FC(1)中:
64F表⽰将要构造的Mat对象中每⼀个数值占⽤64bit且是浮点数,即占8字节的double类型,32F就是占4字节(32bit)的float类型
C(n)表⽰通道数,当n=1时,即构造单通道矩阵(⼆维矩阵),当n>1时构造的是n通道矩阵(三维矩阵),可理解为由n个⼆维矩阵叠加形成的三维矩阵
后⾯⼏种初始化⽅式⾥CV_64FC1等末尾的1也是表⽰通道数
2. 常⽤属性/函数
m为⼀个Mat对象。
m.dims m的维数,单通道是⼆维矩阵,多通道是三维矩阵
vs编程软件m.channels() m的通道数
m.at<int>(r,c) 第r⾏,第c列的值,从0开始(即矩阵的第⼀个数是第0⾏第0列)。<>中的数据类型应与m中存储的数据类型⼀致。
下⾯的程序构造了⼀个2⾏4列的矩阵并遍历输出它的每个元素
#include <opencv2/core/core.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
Mat m =(Mat_<float>(2,4)<<1,2,3,4,5,6,7,8);
for(int r =0; r < m.rows; r++)
{
for(int c =0; c < m.cols; c++)
cout << m.at<float>(r, c)<<' ';
cout << endl;
}
return0;
}
输出:
1 2 3 4
5 6 7 8
m.isContinuous() 若为真则m中⾏与⾏是连续存储
m.ptr<int>(r) 指向第r⾏⾸地址的指针。下⾯的代码演⽰了通过ptr函数遍历Mat,输出和上⾯的完全相同
for(int r =0; r < m.rows; r++)
{
const float* ptr = m.ptr<float>(r);
for(int c =0; c < m.cols; c++)
cout << ptr[c]<<" ";
cout << endl;
}
split(mm,planes) 分离多通道矩阵mm为多个单通道矩阵并保存在动态数组planes中,其中planes的声明为
std::vector<Mat> planes;
(注:需要#include <vector>)
merge(planes,n,mm) 将n个单通道Mat合并为⼀个多通道Mat,其中mm为多通道Mat,planes为Mat数组,n为planes数组的长度,即有⼏个单通道Mat
以三通道矩阵为例,plane定义如下:
Mat planes[]={plane0,plane1,plane2};
也可以使⽤merge函数的重载:
merge(planes,mm) 其中planes为std::vector<Mat>动态数组,mm为Mat类对象
对矩阵的某个部分进⾏处理时经常需要获取其连续⾏或者连续列,或者说矩阵的⼦矩阵:
其中Range(i,j) 表⽰连续整数序列 [ i , j ) 注意这是⼀个左闭右开区间,类似于python中的range
⽰例:
Mat r_wRange(Range(1,4));//获取m的第1~第3⾏
也可以直接使⽤它的重载,不写Range:
Mat r_wRange(1,4);//效果同上
那么类似的也有获取Mat连续列的函数:
需要注意的是,上⾯两个函数返回的⼦矩阵是对原矩阵的引⽤。即如果修改⼦矩阵r_range,原矩阵相应地也会被改变。如果不想改变原矩阵中的值,即想要获取⼀个拷贝的⼦矩阵,可以使⽤clone函数:
m.clone() 返回m的拷贝
即:
Mat r_wRange(1,4).clone();//获取m的第1~3⾏构成的矩阵
这样获得的r_range和原矩阵m就没有任何联系了
3. 运算
加减乘
OpenCV重载了Mat类矩阵加 减 乘法的运算符,因此,矩阵的加 减 乘 只需要:
Mat dst = src1 + src2;src1和src2必须为⾏列数相同的矩阵否则会引发异常
下⾯的例⼦演⽰了两个uchar类型的矩阵相加之后输出结果
uchar取值范围为0~255,是图像处理中常⽤的数据类型
Mat src1 =(Mat_<uchar>(2,3)<<243,123,32,23,2,65);
Mat src2 =(Mat_<uchar>(2,3)<<100,53,72,238,26,64);
Mat dst = a + b;
for(int i =0; i < ws; i++)
{
for(int j =0; j < ls; j++)
printf("%d ",dst.at<uchar>(i, j));
cout << endl;
}
运⾏结果:
255 176 104
255 28 129
当相加结果超过255时会截断成255
当我们把上⾯第三⾏的+号改成-号之后运⾏结果如下:
143 70 0
0 0 1
相减结果⼩于0时截断成0
注意矩阵的乘法只能⽤于float和double类型的Mat对象,且必须满⾜矩阵乘除法的数学规定。否则会在运⾏时产⽣异常
点乘 点除
矩阵的的点乘可以⽤成员函数mul
Mat dst=src1.mul(src2)
即⽤src1的每⼀个元素乘以src2对应位置上的元素。src1和src2的数据类型必须⼀致,没有矩阵乘法那样的类型限制点除直接使⽤/运算符,没有矩阵乘法那样的类型限制
指数,对数
注意这⾥的指数和对数运算是对矩阵中每⼀个元素进⾏的。
pow(src,k,dst) 指数运算,将src的k次⽅保存在dst中,k必须为整数。
⽰例:
Mat src =(Mat_<uchar>(2,2)<<1,3,4,16);
Mat dst;
pow(src,2, dst);
cout << dst << endl;
输出:(因为是uchar类型,所以⼤于255的截断成255了,这是本⽂最后⼀次解释)
[ 1, 9;
16, 255]
当k不为整数时,Mat的类型需为float或double,否则会产⽣运⾏时异常
4. 读取图像并转换为Mat(图像数字化)
imread函数的定义如下
Mat imread( const String& filename, int flags = IMREAD_COLOR );
该函数以指定的模式(flag)读取图像⽂件,定义在<opencv2/highgui/highgui.hpp>头⽂件中
flags允许的值:
IMREAD_COLOR 彩⾊图像
IMREAD_GRAYSCALE 灰度图像(如果filename指定的是彩⾊图像,则会转换成灰度图像显⽰)
IMREAD_ANYCOLOR 任意图像(⾃适应)
⽰例:
Mat img =imread("1.jpg",IMREAD_ANYCOLOR);//读取同⽬录下的1.jpg⽂件
if(!pty())
{
string title ="Picture";
namedWindow(title, WINDOW_AUTOSIZE);//新建⼀个标题为title的窗⼝,根据内容⾃适应⼤⼩
imshow(title,img);//将img显⽰在标题为title的窗⼝中
waitKey(0);//等待任意按键关闭图像,如果不加这个则窗⼝会⼀闪⽽过
}
运⾏效果:(PS : 这是我的QQ头像)
单通道Mat灰度图像
我们打开windows⾃带的画图软件,先把画布的⼤⼩调整为20x20像素
然后我们来画⼀个简单的笑脸:
保存为smile.bmp
然后我们来读取图像并打印Mat的值:
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
Mat img =imread("smile.bmp",IMREAD_ANYCOLOR);
if(!pty())
{
string title ="Picture";
namedWindow(title, WINDOW_AUTOSIZE);
imshow(title,img);
waitKey(0);
cout << img << endl;
}
return0;
}
我们画的图像显⽰出来的了,很⼩,因为我们这⾥没有缩放,20*20=400个像素在如今1080P的屏幕上就是很⼩⼀块,如果你⽤的是2K 或者更⾼分辨率的屏幕它会⼩得更离谱
随便按⼀个键:
看,我们得到的正是⼀个20*20的矩阵,每个数字代表着对应的像素点的颜⾊,255是纯⽩⾊,0是纯⿊⾊,在这⾥255和0也勾勒出了⼀个笑脸的模样。这就是图像数字化的魅⼒。
我们可以把img⾥的255全改成⼀个⼩⼀点的灰度值(灰度值越⼩颜⾊越深)再显⽰:
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
Mat img =imread("smile.bmp",IMREAD_ANYCOLOR);
string title ="Picture";
namedWindow(title, WINDOW_NORMAL);//WINDOW_NORMAL模式下的窗⼝可以被⽤户改变⼤⼩
for(int r =0; r < ws; r++)
{
for(int c =0; c < ls; c++)
if(img.at<uchar>(r, c)==255)
img.at<uchar>(r, c)=200;
}
cout << img << endl;
imshow(title, img);
waitKey(0);
return0;
}
看,背景变灰了
我们再试试颜⾊反转:
(修改上⾯的for循环)
for(int r =0; r < ws; r++)
{
for(int c =0; c < ls; c++)
img.at<uchar>(r, c)=255- img.at<uchar>(r, c);
}
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论