既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
4、基本操作源代码
void QuickDemo::mat\_creat\_Demo(Mat &image)
{
Mat mClone;
Mat mCopy;
mClone = image.clone();//克隆
image.copyTo(mCopy);//复制
//创建空白mat CV\_8UC1表示8位 无符号类型 channel通道为1
Mat mEmpty = Mat::zeros(Size(8,8),CV_8UC1);//创建大小为8x8的单通道的空mat
//注意使用ones初始化为1的时候是只把每个像素的第一个通道初始化为1,其他还是0
Mat mOnes = Mat::ones(Size(8, 8), CV_8UC3);
std::cout << "ones ;Size(8,8) CV\_8UC3:" << mOnes << std::endl;
std::cout << "Size(8,8) CV\_8UC1:" << mEmpty << std::endl;
std::cout << "width=" << mEmpty.cols << ",height=" << mEmpty.rows << ",channele=" << mEmpty.channels() << std::endl;
std::cout << "====================" << std::endl;
Mat mEmptyC3 = Mat::zeros(Size(8, 8), CV_8UC3);//创建大小为8x8的3通道的空mat
std::cout << "Size(8,8) CV\_8UC3:" << mEmptyC3 << std::endl;
std::cout << "width=" << mEmptyC3.cols << ",height=" << mEmptyC3.rows << ",channele=" << mEmptyC3.channels() << std::endl;
//赋值操作
mEmptyC3 = 127;//这种赋值方法也同样只是会对每个像素的第一个通道赋值为127其他的不变
std::cout << "mEmptyC3 = 127;:" << mEmptyC3 << std::endl;
//创建底色画布的
mEmptyC3 = Scalar(127, 127, 127);//如果想要操作每个通道则要使用Scalar,
std::cout << "mEmptyC3 = Scalar(127, 127, 127);:" << mEmptyC3 << std::endl;
imshow("创建窗口", mEmptyC3);//注意imshow支持8位或浮点数类型 有限制的
//验证赋值操作 其实数据区复制,只是指针赋值,因此=操作会改变原来mat
//但是clone和copyto是产生新的数据区,不会影响原来的。
Mat mEqualOpr = mEmptyC3;
mEqualOpr = Scalar(0, 255, 255);
imshow("创建窗口1", mEmptyC3);
return;
}
五、图像像素的遍历读写
在opencv当中一切图像皆mat。
遍历一张图片mat的每个像素点的值并对其进行读写操作。
1、数组方式访问
使用at方法进行访问像素点,根据at<参数类型>来进行对像素数据的读取,决定读取几个如uchar适合单通道会读取一个数据、Vec3b适合三通道会读取三个数据。
2、指针方式访问
使用ptr方法进行访问,这里就直接指定类型,因为指针是按字节访问的即可,不需要读取多个。
//数组访问方式
void QuickDemo::pixel\_visit\_demo(Mat &image)
{
int w = image.cols;
int h = image.rows;
int channel = image.channels();
for (int row = 0; row < h; row++)
{
for (int cols = 0; cols < w; cols++)
{
if (channel == 1)//单通道 灰度图
{
int p = image.at<uchar>(row, cols);//读操作 uchar无符号字节类型 转换为int
image.at<uchar>(row, cols) = 255 - p;//保证数据在255之间,数据不越出。
}
else if (channel == 3)//三通道
{
Vec3b bgr = image.at<Vec3b>(row, cols);//有Vec3b这样的数据类型 一次读取三个值,重定义Vec<uchar, 3>
//写
image.at<Vec3b>(row, cols)[0] = 255 - bgr[0];
image.at<Vec3b>(row, cols)[1] = 255 - bgr[1];
image.at<Vec3b>(row, cols)[2] = 255 - bgr[2];
}
}
}
imshow("像素读写显示",image);
}
//指针访问方式
for (int row = 0; row < h; row++)
{
uchar\* current_row = image.ptr<uchar>(row);
for (int cols = 0; cols < w; cols++)
{
if (channel == 1)//单通道 灰度图
{
\*current_row++ = 255 - \*current_row;//保证数据在255之间,数据不越出。
}
else if (channel == 3)//三通道
{
//每次访问一个字节,并且进行++移动
\*(current_row++) = 255 - \*current_row;
\*(current_row++) = 255 - \*current_row;
\*(current_row++) = 255 - \*current_row;
}
}
}
六、图像像素的算术操作
就是mat支持所有的加减乘除的所有算术操作,都可以进行实践。
并且同时opencv其实自己也提供了相对应的函数接口如add、subract,divide、multiply。
可以和Scalar标量本身也可以和Mat本身进行加减乘除。
Mat dst;
Scalar表示标量
dst = image + Scalar(50, 50, 50);//也可以使用opencv自带api;add()方法
imshow("加法操作", dst);
dst = image - Scalar(50, 50, 50);
imshow("减法操作", dst);
dst = image / Scalar(4, 4, 4);//也可以使用divide()方法
imshow("除法操作", dst);
/\*dst = image \* Scalar(2, 2, 2);直接乘法会存在数据溢出报错,opencv中提供了专门的乘法函数
imshow("乘法法操作", dst);\*/
//opencv提供的乘法函数 他会将超出范围的数据自动截取
//使用saturate\_cast<uchar>函数 小于0则是0 大于255则是255
//注意需创建大小与image相同的mat进行乘法 可以这样 直接Scalar也可以
//Mat m = Mat::zeros(image.size(), image.type());
//m = Scalar(2, 2, 2);
multiply(image, Scalar(2, 2, 2), dst);
imshow("乘法操作", dst);
//自定义加法操作
Mat m = Mat::zeros(image.size(), image.type());
dst = Mat::zeros(image.size(), image.type());
m = Scalar(50, 50, 50);
int w = image.cols;
int h = image.rows;
int channel = image.channels();
for (int row = 0; row < h; row++)
{
uchar\* current_row = image.ptr<uchar>(row);
uchar\* current_m = m.ptr<uchar>(row);
uchar\* current_dst = dst.ptr<uchar>(row);
for (int cols = 0; cols < w; cols++)
{
//每次访问一个字节,并且进行++移动
//关键 saturate\_cast 保证数据类型在范围内
\*(current_dst++) = saturate_cast<uchar>(\*current_row++ + \*current_m++);
\*(current_dst++) = saturate_cast<uchar>(\*current_row++ + \*current_m++);
\*(current_dst++) = saturate_cast<uchar>(\*current_row++ + \*current_m++);
}
}
imshow("自定义加法操作", dst);
return;
对应效果图
七、TrackBar滚动条操作实现亮度和对比度比较createTrackbar
创建TrackBar滚动条的
CV_EXPORTS int createTrackbar(const String& trackbarname, const String& winname,
int\* value, int count,
TrackbarCallback onChange = 0,
void\* userdata = 0);
const String& trackbarname,进度条名字
const String& winname,需绑定到的目标窗口名
int\* value, int count,当前值 最大值
TrackbarCallback onChange = 0回调函数,进度条触发
void\* userdata = 0 传参可以任何类型但是要注意类型转换
CV_EXPORTS_W void addWeighted(InputArray src1, double alpha, InputArray src2,
double beta, double gamma, OutputArray dst, int dtype = -1);
用于两张图片的混合,第一张图占比多少,第二张占比多少,
double alpha,图片1权重
double beta 图片2权重
double gamma 图片1和图片2求和后偏移量
最后结果输出的公示就是
dst = src1\*alpha + src2\*beta + gamma;
源代码
//这个int b就是亮度调整传过来的值
//void \*userdata 任何类型的指针 只是要类型转换
static void on\_track(int b, void \*userdata)
{
Mat image = \*((Mat \*)userdata);
Mat mTemp = Mat::zeros(image.size(), image.type());
Mat dst = Mat::zeros(image.size(), image.type());
addWeighted(image, 1.0, mTemp, 0, b, dst);//图像混合 根据各种权重
imshow("亮度调整", dst);
}
static void on\_constrast(int b, void \*userdata)
{
Mat image = \*((Mat \*)userdata);
Mat mTemp = Mat::zeros(image.size(), image.type());
Mat dst = Mat::zeros(image.size(), image.type());
double constrat = b / 100.0;
addWeighted(image, constrat, mTemp, 0.0, 0, dst);
imshow("亮度调整", dst);
}
void QuickDemo::tracking\_bar\_demo(Mat &image)
{
int lightness = 50;//亮度值
namedWindow("亮度调整", WINDOW_AUTOSIZE);
int max_value = 100;
int constract_value = 100;
//on\_track 回调函数
createTrackbar("Value Bar:", "亮度调整", &lightness, max_value, on_track, (void \*)(&image));
createTrackbar("constrat Bar:", "亮度调整", &constract_value, 200, on_constrast, (void \*)(&image));
//首先要调用一次,否则刚开始是没有图像的,要点击才有,第二个参数没有就传0
on\_track(50, &image);
return;
}
八、键盘响应waitKey()
int c = waitKey(100);//Waits for a pressed key. 等待100ms
//永远要记得一点在做视频分析的时候waitKey一定要传入1、waitKey(1)
void QuickDemo::key\_demo(Mat &image)
{
Mat dst = Mat::zeros(image.size(), image.type());
while (true)
{
int c = waitKey(100);//Waits for a pressed key. 等待100ms
//std::cout << c << std::endl; 暂时先测试按下什么按键在opencv中对应的值
if (c == 27)//esc 退出27
{
break;
}
else if (c == 49)//点击图片按数字1进行灰度转换
{
std::cout << "you enter key #1" << std::endl;
cvtColor(image, dst, COLOR_BGR2GRAY);
}
else if (c == 50)//点击图片按数字2进行hsv转换
{
std::cout << "you enter key #2" << std::endl;
cvtColor(image, dst, COLOR_BGR2HSV);
}
else if (c == 51)//点击图片按数字3进行亮度增加转换
{
std::cout << "you enter key #3" << std::endl;
add(image, Scalar(50, 50, 50), dst);
}
imshow("显示操作结果图片", dst);
}
return;
}
九、opencv自带的颜色表applyColorMap;查找表LUT
Look Up Table(LUT) 查找表
查找表在我们的图像基础或简单的颜色匹配或伪色彩增强等当中是十分重要的,有时候我们可以看到红外的照片或各种各样风格的照片是怎么样做到的呢?这就涉及到具体细节的知识点就是颜色查找表,就是要把原来的某种颜色匹配到另外一种颜色上面去,就是把低对比度的图转换到高对比度的图上去。
这个查找表可以是一个单调的增函数或者单调的减函数或者直接值匹配的这种也行,在图像像素这个层次而言为什么需要查找表,就是一副图像大概几万个像素而每个像素都要进行转换都要去进行函数转换的话那么十分耗资源,而查找表就是预先做一次转换把0-255都做一次匹配,再图像转换的时候就需要去查找匹配的值就可以不需要每次都去调用函数进行转换了,节约了大量的资源,这才是我们使用LUT查找表的真正原因。LUT查找表在实践很多数据处理场景当中只要你知道你需要处理的数据范围是什么,变化公式是什么,最后的结果是什么,那么对单个元素的变化,很多时候我们都通过LUT查找表来做,这样来节省资源时间,将计算步骤得到很多的减少。
作用;伪色彩加强和灰度或RGB图像的二值图像,加快计算速度
伪色彩函数applyColorMap
CV_EXPORTS_W void applyColorMap(InputArray src, OutputArray dst, int colormap);
src;源图像
dst;目标图像
colormap;提供的色彩图代码值。(参见:ColormapTypes 枚举数据类型)也可以自定义。
可以实现彩色图片颜色变化,以及灰度图颜色增强。
//色彩表
void QuickDemo::color\_style\_demo(Mat &image)
{
//enum ColormapTypes 支持的20种色彩空间
int colorMap[] =
{
COLORMAP_AUTUMN,
COLORMAP_BONE,
COLORMAP_JET,
COLORMAP_WINTER,
COLORMAP_RAINBOW,
COLORMAP_OCEAN,
COLORMAP_SUMMER,
COLORMAP_SPRING,
COLORMAP_COOL,
COLORMAP_HSV ,
COLORMAP_PINK ,
COLORMAP_HOT,
COLORMAP_PARULA,
COLORMAP_MAGMA,
COLORMAP_INFERNO,
COLORMAP_PLASMA,
COLORMAP_VIRIDIS,
COLORMAP_CIVIDIS,
COLORMAP_TWILIGHT,
COLORMAP_TWILIGHT_SHIFTED
};
Mat dst;
int index = 0;
while (true)
{
int c = waitKey(2000);//Waits for a pressed key. 等待100ms
if (c == 27)//esc 退出27
{
break;
}
//伪色彩函数
applyColorMap(image, dst, colorMap[index%19]);
index++;
imshow("颜色风格", dst);
}
}
十、图像像素的逻辑操作(位操作)bite oprocted
位操作对应与或非操作 而opencv中是哪些API与之对应的。
CV_EXPORTS_W void rectangle(InputOutputArray img, Rect rec,
const Scalar& color, int thickness = 1,
int lineType = LINE_8, int shift = 0);
Rect\_<_Tp>::Rect\_(_Tp _x, _Tp _y, _Tp _width, _Tp _height)
: x(_x), y(_y), width(_width), height(_height) {}
InputOutputArray img传入输出
Rect rec,矩形 传入xy宽高
const Scalar& color,填充颜色
int thickness = 1,1表示一个像素去绘制,相当于描边
传入<0表示填充这个矩形的意思.
opencv当中绘制和填充矩形都是这一个函数,用这个大于0 小于0来进行区分
int lineType = LINE_8 图形绘制方面的知识,与像素排列锯齿有关系。
//对Mat实现像素的位操作
void QuickDemo::bitwise\_demo(Mat &image)
{
Mat m1 = Mat::zeros(Size(256, 256), CV_8UC3);
Mat m2 = Mat::zeros(Size(256, 256), CV_8UC3);
Mat m3 = Mat::zeros(Size(256, 256), CV_8UC3);
rectangle(m1,Rect(100, 100, 80, 80), Scalar(255, 255, 0), -1, LINE_8, 0);//-1表示填充矩形,LINE\_8表示利用周围8个像素
rectangle(m2, Rect(150, 150, 80, 80), Scalar(0, 255, 255), -1, LINE_8, 0);
rectangle(m3, Rect(0, 0, 80, 80), Scalar(255, 0, 255),5, LINE_8, 0);//绘制 相当于描边
imshow("m1", m1);
imshow("m2", m2);
imshow("m3", m3);
Mat dst;
bitwise\_and(m1, m2, dst);//相当于交集
imshow("像素位操作与", dst);
bitwise\_or(m1, m2, dst);//全集
imshow("像素位操作或", dst);
bitwise\_not(image, dst);
//dst = ~image;//也是可以的
imshow("像素位操作非", dst);
bitwise\_xor(m1, m2, dst);
imshow("像素位操作异或", dst);
return;
}
十一、图像通道的分离合并混合
分离split
CV_EXPORTS_W void split(InputArray m, OutputArrayOfArrays mv);
OutputArrayOfArrays mv 输出的通道数组 可以用std::vector<Mat> mv;来接收
实践
//图像分离split函数
std::vector<Mat> mv;
//opencv顺序是BGR的
split(image, mv); //分离成三个通道对应三种颜色
imshow("蓝色", mv[0]);
imshow("绿色", mv[1]);
imshow("红色", mv[2]);
合并merge
CV_EXPORTS_W void merge(InputArrayOfArrays mv, OutputArray dst);
InputArrayOfArrays mv 多通道的集合
使用
//合并通道到目标mat
Mat dst;
mv[1] = 0;//只保留蓝色
mv[2] = 0;
merge(mv, dst);
imshow("只显示蓝色", dst);
混合mixChannels
CV_EXPORTS void mixChannels(const Mat\* src, size_t nsrcs, Mat\* dst, size_t ndsts,
const int\* fromTo, size_t npairs);
const Mat\* src 原图数组
size_t nsrcs 操作多少张图像
Mat\* dst 目标图数组
size_t ndsts 操作多少张图像
const int\* fromTo 转换从通道几到通道几 如{0,2, 1,1, 2,0}第一张0通道到第二张2通道
size_t npairs 转换的数据有几对 3对
实践
//混合交互通道
int from_to[] = {0,2, 1,1, 2,0};//通道交互从哪里到哪里 第一张0通道到第二张2通道
mixChannels(&image, 1, &dst, 1, from_to, 3);//支持多张图片 1表示多少张图片 3表示有几对通道交换数据
imshow("通道混合", dst);
十二、色彩空间转换-提取图像替换背景
色彩空间转换cvtColor
提取指定色彩空间范围区域imRange
hsv色彩空间的红橙黄绿蓝顶紫与rgb比较明显的区别界限,
因为hsv相对于rgb的颜色类型要少一些,只有hs两个表示并且只有180*255个颜色,从而就有hsv相对grb有一些纯颜色就有一定的界限,可以使用imRange提取出来的,利用上面那个表的最大最小值。
inRange可实现二值化功能,主要是将在两个阈值内的像素值设置为白色(255),而不在阈值区间内的像素值设置为黑色(0),该功能类似于之间所讲的双阈值化操作。
CV_EXPORTS_W void inRange(InputArray src, InputArray lowerb,
InputArray upperb, OutputArray dst);
InputArray lowerb;下限值
InputArray upperb; 上限值
OutputArray dst输出的图像 只有01的,再结合copyto可以实现图像叠加
实践
Mat hsv;
cvtColor(image, hsv, COLOR_BGR2HSV);
Mat mask;
inRange(hsv, Scalar(35, 43, 46), Scalar(77, 255, 255), mask);//输入hsv图像、 根据绿色作为分界则传入hsv绿色的hsv最大最下值
imshow("inRange mask", mask);
首先要提取像素值在rgb中是很难的,因为rgb255255255颜色值太多太广了而hsv当中只有180*255的色彩空间值,就比较容易对颜色进行提取。
所以当看到一个单一颜色的时候要想去提取他,那么你的第一个反应就应该是要转换为一个色彩比较度高的色彩空间去,如hsv当中去。处理完成之后再返回rgb。
图像叠加copyto函数;源于可以根据mask进行有选择性的拷贝,结合inrange就可以完成不规则图像的iro图像提取。
不规则图像的读取就是将不规则图像生成一个闭合区域,生成一个mask再利用copyto完成任意提取。
inline
void GpuMat::copyTo(OutputArray dst, InputArray mask) const
{
copyTo(dst, mask, Stream::Null());
}
关键在于mask,
image.copyTo(reaback, mask);
/\*
//mask的意思就是image在copy的时候只有像素不为0的像素点部分才会copy到reaback上去。
只是把mask中像素值不为0的像素点从image复制到reaback上去。
\*/
//色彩空间的转换
void QuickDemo::inrange\_demo(Mat &image)
{
Mat hsv;
cvtColor(image, hsv, COLOR_BGR2HSV);
Mat mask;
/\*
inRange可实现二值化功能,
主要是将在两个阈值内的像素值设置为白色(255),而不在阈值区间内的像素值设置为黑色(0),
该功能类似于之间所讲的双阈值化操作。
\*/
inRange(hsv, Scalar(35, 43, 46), Scalar(77, 255, 255), mask);//输入hsv图像、 根据绿色作为分界则传入hsv绿色的hsv最大最下值
imshow("inRange mask", mask);//只有黑白 把绿色都变成1白色 其他变成0黑色
Mat reaback = Mat::zeros(image.size(), image.type());
reaback = Scalar(40, 40, 200);//红底
bitwise\_not(mask, mask);//取反 黑变白 白变黑 就是相当于把绿色部分变成0黑色、人部分变成1白色
imshow("bitwise\_not mask", mask);
/\*
//mask的意思就是image在copy的时候只有像素不为0的点才会copy到reaback上去
相当于只人部分变成1白色部分从image上叠加上去,其他不变
(就是把image人所在区域拷贝到reaback上来)
\*/
image.copyTo(reaback, mask);
imshow("roi区域提取", reaback);
}
十三、像素值统计信息
像素值统计一般就是最小值 最大值 均值mean 标准方差standard deviationn
涉及的API就是
最大最小值;minMaxLoc 只用于单通道 使用前要split分割
计算均值与标准方差;meanStdDev 方差低表示图片差异度低从而在图像处理中可以过滤掉一些信息
如何学习opencvAPI以及在合适的场景选择他们完成功能这就是学习opencv的目标。
CV_EXPORTS_W void 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());
值和坐标点都是传入指针,最后的mask就是用于iro非规则范围读取使用的
一定要记得只使用与单通道 要进行split分割的
CV_EXPORTS_W void meanStdDev(InputArray src, OutputArray mean, OutputArray stddev,
InputArray mask=noArray());
OutputArray mean,均值输出
OutputArray stddev 方差输出
InputArray mask=noArray() iro非规则图像范围类读取
使用
Mat mean, stddev;
meanStdDev(image,mean,stddev);
输出是每个通道一个值 mean.at<double>(0)表示通道1的均值
实践
//进行像素值统计
void QuickDemo::pixel\_statistic\_demmo(Mat &image)
{
double minv, maxv;//注意是double类型
Point minLoc, maxLoc;//最大最小的地址
std::vector<Mat> mv;
split(image, mv);
for (int i = 0; i < mv.size(); i++)
{
minMaxLoc(mv[i], &minv, &maxv, &minLoc, &maxLoc, Mat());//传入参数必须是单通道的、得先split
std::cout << "minvalue:" << minv << " maxvalue:" << maxv << std::endl;
}
Mat mean, stddev;
meanStdDev(image,mean,stddev);//第四个参数mask就是iro不规则区域计算
std::cout << "mean:" << mean << std::endl;
std::cout << "mean[0]:" << mean.at<double>(0) << std::endl;
//方差小就是图像的对比度差异度低、有效信息低
//因此在图像分析当中,可以根据方差值小表示周围差异性小 从而是可以过滤掉一些东西的
std::cout << "stddev:" << stddev << std::endl;
}
十四、图像几何形状绘制
分为各种形状的绘制和填充绘制,
如rectangle 中的int thickness参数 -1表示填充、其他的表示绘制宽度。
1、常规图形绘制
Rect 矩形对象类
rectangle 绘制矩形的方法
Point 表示点坐标
circle 绘制园
line 绘制线
RotatedRect 椭圆对象
ellipse绘制椭圆
可以使用addWeighted根据与图像的占比不同实现在图像上关键部位进行叠加几何形状
实际
//几何形状绘制
void QuickDemo::drawing\_demo(Mat &image)
{
//坐标的原点是从左上角为0 0
//绘制矩形是可以有类型定义的
Rect rect;
rect.x = 150;
rect.y = 20;
rect.width = 70;
rect.height = 100;
Mat bg = Mat::zeros(image.size(), image.type());
//rectangle(bg, rect, Scalar(0, 0, 255), 5, 0);
rectangle(bg, rect, Scalar(0, 0, 255), -1, 0);//thickness参数改成小于0则表示填充
circle(bg, Point(200, 200), 50, Scalar(255, 0, 0), -1, 0);//画圆
line(bg, Point(100, 100), Point(255, 255), Scalar(0,0,0),8, 8, 0);//lineType图像锯齿问题 shift参数0 是相对左上角位置偏移亮的意思
RotatedRect rrt;//椭圆
rrt.center = Point(300,300);
rrt.size = Size(50,100);
rrt.angle = 0;
ellipse(bg, rrt, Scalar(0, 255, 255), 2, 8);
Mat dst;
addWeighted(image, 0.7, bg, 0.3, 0, dst);//image占比0.7,bg占比0.3 叠加
imshow("绘制几何", dst);
}
2、随机数产生绘制
RNG 产生随机数对象 传入种子一般为时间
RNG::uniform(int a, int b) 产生a-b范围内的随机值
实践
//随机数和随即绘制
void QuickDemo::randomdrawing\_demo(Mat &image)
{
Mat canvas = Mat::zeros(Size(500, 500), CV_8UC3);
int w = canvas.cols;
int h = canvas.rows;
RNG rng(12345);//产生随机数 传入种子 一般传入时间
while (true)
{
int c = waitKey(10);
if (c == 27)
{
break;
}
int x1 = rng.uniform(0, w);//在0 - w范围内产生一个随机数
int y1 = rng.uniform(0, h);
int x2 = rng.uniform(0, w);//在0 - w范围内产生一个随机数
int y2 = rng.uniform(0, h);
int b = rng.uniform(0, 255);
int g = rng.uniform(0, 255);
int r = rng.uniform(0, 255);
//canvas = Scalar(0, 0, 0);每次画布更新 达到每次图像只有一条线
line(canvas, Point(x1, y1), Point(x2, y2), Scalar(b, g, r), 1, LINE_AA, 0);//lineType图像锯齿问题
imshow("随机绘制", canvas);
}
return;
}
3、多边形的填充绘制fillPoly、polylines、drawContours
fillPoly 只能填充
polylines 只能绘制 如果需要表现出来既有填充又会绘制需要注意两者顺序
drawContours 可以绘制多个多边形
实践
//绘制多边形
void QuickDemo::polylinedrawing\_demo(Mat &image)
{
Mat canvas = Mat::zeros(Size(500, 500), CV_8UC3);
Point p1(100, 100);
Point p2(350, 100);
Point p3(450, 280);
Point p4(320, 450);
Point p5(80, 400);
std::vector<Point> vc_pts;
vc_pts.push\_back(p1);
vc_pts.push\_back(p2);
vc_pts.push\_back(p3);
vc_pts.push\_back(p4);
vc_pts.push\_back(p5);
//要实现边框加填充就要注意fillPoly、polylines的顺序
fillPoly(canvas, vc_pts, Scalar(255, 0, 255), 8, 0);//只能填充
polylines(canvas, vc_pts, true, Scalar(0, 0, 255), 1, LINE_AA, 0);//这里的thickness只能传大于0 不能填充只能绘制
imshow("多边形绘制", canvas);
//显示多个多边形函数 绘制填充都可以选择
std::vector<std::vector<Point>> contous;
contous.push\_back(vc_pts);
//contourIdx索引显示第几个,-1表示都显示出来
//thickness 可以传入-1表示填充
drawContours(canvas, contous, -1, Scalar(255, 0, 0), -1);
imshow("多边形绘制1", canvas);
return;
}
十五、鼠标操作及响应 在图形上提取鼠标框选区域setMouseCallback
绑定鼠标事件setMouseCallback
void setMouseCallback(const String& winname, MouseCallback onMouse, void\* userdata = 0);
const String& winname;窗口名字
MouseCallback onMouse 鼠标响应后调用的回调函数、注意其原型需要是
typedef void(\* cv::mouseCallBack)(int event, int x, int y, int flags, void \*userdata)
void\* userdata = 0 用于传给回调函数参数的
int event 表示当前发生的鼠标事件是什么
int x, int y 当前鼠标的坐标位置
int flags, 当前鼠标事件的flags值
鼠标event事件的官方截图
opencv官方文档链接
实践 还实现了ROI区域的读取、注意几个细节
每次都在最新的image上绘制采用一个临时mat先clone再每次绘制前进行copyto最初的到image上
注意鼠标回调事件的传参,如当前demo传入Mat image 在该图像上操作
代码
//鼠标操作及相应
Point sp(-1, -1);
Point ep(-1, -1);
Mat tempImage;
static void on\_draw(int event, int x, int y, int flags, void \*usrdata)
{
Mat image = \*((Mat \*)usrdata);
if (event == EVENT_LBUTTONDOWN)//鼠标左键按下事件
{
sp.x = x;
sp.y = y;
std::cout << "start Point: " << sp << std::endl;
}
else if (event == EVENT_LBUTTONUP)//左键松开
{
ep.x = x;
ep.y = y;
int dx = ep.x - sp.x;
int dy = ep.y - sp.y;
if (dx > 0 && dy > 0)
{
Rect box(sp.x, sp.y, dx, dy);
//注意顺序 不然提取区域会有红色矩形框 并且要保证矩形范围在图形内否则会崩溃
tempImage.copyTo(image);
imshow("ROI区域", image(box));//截取鼠标款选的区域
rectangle(image, box, Scalar(0, 0 , 255), 2);
imshow("鼠标绘制", image);
//给下次绘制初始化
sp.x = -1;
sp.y = -1;
}
std::cout << "end Point: " << ep << std::endl;
}
else if (event == EVENT_MOUSEMOVE)
{
if (sp.x >0 && sp.y > 0)
{
ep.x = x;
ep.y = y;
int dx = ep.x - sp.x;
int dy = ep.y - sp.y;
if (dx > 0 && dy > 0)
{
Rect box(sp.x, sp.y, dx, dy);
tempImage.copyTo(image);//保证每次 绘制image都是最初的
rectangle(image, box, Scalar(0, 0, 255), 2);
imshow("鼠标绘制", image);
}
}
}
return;
}
void QuickDemo::setMouseCallback\_demo(Mat &image)
{
namedWindow("鼠标绘制", WINDOW_AUTOSIZE);
setMouseCallback("鼠标绘制", on_draw, (void \*)&image);
tempImage = image.clone();
imshow("鼠标绘制", image);
}
十六、图像像素数据类型转换及归一化convertTo、normalize
//归一化处理就是处理浮点数,到0-1之间,因此执行归一化处理首先要将图像数据转换为浮点数类型再进行归一化。并且浮点数类型的图片显示必须经过归一化处理把数据变成0-1之间才能正常显示出来,之后如何转换回来,就是归一化处理之后的像素值乘以255,再converto转换会int类型
数据类型转换converto
归一化处理normalize
inline
void GpuMat::convertTo(OutputArray dst, int rtype, double alpha, double beta) const
{
convertTo(dst, rtype, alpha, beta, Stream::Null());
}
int rtype 数据类型 CV_32F 32位浮点数类型
double alpha;每个像素值乘以alpha
double beta;每个像素值加上beta
输出的时候每个像素值是否需要乘以alpha加上beta值
归一化处理
CV_EXPORTS_W void normalize( InputArray src, InputOutputArray dst, double alpha = 1, double beta = 0,
int norm_type = NORM_L2, int dtype = -1, InputArray mask = noArray());
double alpha = 1, double beta = 0,归一化处理值的上下限
int norm_type = NORM_L2 归一化处理的算法
int dtype = -1, 通道数变化 -1表示与原图像一致
InputArray mask = noArray() ROI图像提取 也就是只归一化mat为非0的区域、可以使用inRange函数提取mask
图像归一化处理主要用在深度学习的建立模型的时候使用
实践
//像素类型转换及归一化处理
void QuickDemo::norm\_demo(Mat &image)
{
Mat dst;
std::cout << image.type() << std::endl;
image.convertTo(image, CV_32F);//转化为32位的浮点数
std::cout << image.type() << std::endl;//type()输出的值是opencv重定义的枚举类型值
normalize(image, dst, 1.0, 0, NORM_MINMAX);
std::cout << dst.type() << std::endl;
imshow("图像数据归一化", dst);
}
十七、图像放缩及 插值算法 resize
图像的resize大小放大缩小实现中就使用了插值算法、其中插值算法有很多种如双立方插值、双线性内插值、Lanczos采用放缩算法等,其实只要涉及图像的像素提取移动都是用到插值算法的(几何变化、透视变化、插值计算像素resize等系列情况)
CV_EXPORTS_W void resize( InputArray src, OutputArray dst,
Size dsize, double fx = 0, double fy = 0,
int interpolation = INTER_LINEAR );
Size dsize, 目标图像的大小
double fx = 0, double fy = 0 如果size写入的是0,0 则安装fx,fy进行水平 竖直方向的放缩
int interpolation = INTER_LINEAR 选择的插值方法
//图像放缩及插值
void QuickDemo::resize\_demo(Mat &image)
{
Mat zoomin;
Mat zoomax;
int w = image.cols;
int h = image.rows;
resize(image, zoomin, Size(w/2, h/2), 0, 0, INTER_LINEAR);
imshow("zoomin", zoomin);
resize(image, zoomax, Size(w \* 1.5, h \* 1.5), 0, 0, INTER_LINEAR);
imshow("zoomax", zoomax);
}
十八、图像的翻转 平移
1、图像翻转flip
图像的翻转其实就是获取他的镜像、
CV_EXPORTS_W void flip(InputArray src, OutputArray dst, int flipCode);
int flipCode指定如何翻转数组的标志;
0表示绕x轴翻转,就是上下翻转
正值(例如1)表示绕y轴翻转。就是左右翻转
负值(例如-1)意味着在两个轴上翻转。上下左右都翻转
//翻转
void QuickDemo::flip\_demo(Mat &image)
{
Mat dst;
flip(image, dst, 0);//上下翻转
imshow("上下翻转", dst);
flip(image, dst, 1);//大于0 左右翻转
imshow("左右翻转", dst);
flip(image, dst, -1);//小于0 上下左右都翻转 就是180度旋转
imshow("180度旋转", dst);
}
2、图像旋转getRotationMatrix2D、warpAffine
图像的旋转 opencv也是提供了api的是warpAffine()函数,
注意图像的旋转是需要借助矩阵的,因为图像旋转其实每个像素点都是有变化的。矩形其实opencv中提供了api,getRotationMatrix2D()方法传入旋转角度就会返回矩阵的
inline
Mat getRotationMatrix2D(Point2f center, double angle, double scale)
{
return Mat(getRotationMatrix2D\_(center, angle, scale), true);
}
Point2f center 图像的中心位置
double angle 旋转角度
double scale 这个还可以进行图像的放缩,输入目标图像的放缩比例
计算旋转后图形的宽高几何公式来源图
实现旋转代码
//旋转
void QuickDemo::rotate\_demo(Mat &image)
{
Mat dst, M;
int w = image.cols;
int h = image.rows;
M = getRotationMatrix2D(Point2f(w/2, h/2), 45, 1.0);//得到旋转45度的矩阵
//计算旋转后的图片大小 这个是得到旋转Mat可以知道的对应的cos、sin
double cos = abs(M.at<double>(0, 0));
double sin = abs(M.at<double>(0, 1));
//计算旋转后的新宽高 根据几何知识
int nh = cos\*w + sin\*h;
int nw = sin\*w + cos\*h;
//计算偏移量 修改中心的偏移量 因为warpAffine需要传入的mat的中心坐标
M.at<double>(0, 2) += (nw/2 - w/2);
M.at<double>(1, 2) += (nh / 2 - h / 2);
warpAffine(image, dst, M, Size(nw, nh), INTER_LINEAR, 0, Scalar(255, 0, 0));
imshow("旋转演示", dst);//没有遮挡的
}
十九、摄像头及视频处理
opencv保存视频是只处理视频不处理音频的,如果要处理音视频那么就只能使用ffmpeg了,但是在使用ffmpeg的时候可以合理利用opencv处理视频图像的手段,更好的完成功能。
注意opencv保存视频也是有大小限制的,最大2G。
视频的访问及相关属性
VideoCapture类 用于抽象化视频文件的对象
VideoCapture captureCamear(0);
传入0表示获取默认摄像头设备 打开摄像头流之后captureCamear对象就抽象为摄像头设备进行读取视频流等操作
VideoCapture captureCamear("C:/Users/20531/Desktop/1.mp4");
也可以传入视频文件,但是注意读取视频文件的时候可能读取失败 读取出来的宽高为0 ,这需要添加一个库
/\*
//如果读取视频文件的帧宽高失败则需要
将opencv安装目录D:\opencv\build\x64\vc14\bin中的opencv\_videoio\_ffmpeg440\_64.dll复制
到生成项目的.exe所在的文件(Debug/Release)中。
(因为我用OpenCV版本是4.4,所以ffmpeg440)
\*/
VideoCapture的系列API
如captureCamear.get(属性)//有很多宏控制,可以返回对应的视频参数,
当然也可以set,但是注意如果是摄像头的话 set的参数摄像头硬件是否支持
captureCamear.read(Mat)//读取视频帧图像
将视频读取成一帧帧的mat图像之后就可以对其每个mat做处理就达到对视频流做处理的效果。
注意VideoCapture对象占用资源 需要在不再使用之前进行realease()
视频的写入
VideoWriter类对象完成对视频的写入
其中一种构造函数
CV_WRAP VideoWriter(const String& filename, int fourcc, double fps,
Size frameSize, bool isColor = true);
const String& filename写入视频的文件全路径
int fourcc, 采用的编解码类型,这个可以从源视频流对象那里获取、captureCamear.get(CAP_PROP_FOURCC)
double fps,帧率
Size frameSize,宽高 最好也从原视频获取,但是如果就是要对视频进行放缩的话 要与后面的mat写入对应
如
VideoWriter writer("D:/test.mp4", captureCamear.get(CAP_PROP_FOURCC), fps, Size(frame_width, frame_height), true);
//写入API write即可
writer.write(frameCamear);//写入文件
注意视频写入的时候可能会报一个错误 提示
could not open codec “libopenh264” : Unspecified error错误,
则需要根据提示下载对应版本的dll库,放到.exe的运行目录下即可
参考博客
openh264库下载网站
缺少一个从一个视频扣ROI图到另外一个视频上叠加的案例
实践
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
fourcc, 采用的编解码类型,这个可以从源视频流对象那里获取、captureCamear.get(CAP_PROP_FOURCC)
double fps,帧率
Size frameSize,宽高 最好也从原视频获取,但是如果就是要对视频进行放缩的话 要与后面的mat写入对应
如
VideoWriter writer(“D:/test.mp4”, captureCamear.get(CAP_PROP_FOURCC), fps, Size(frame_width, frame_height), true);
//写入API write即可
writer.write(frameCamear);//写入文件
注意视频写入的时候可能会报一个错误 提示
could not open codec “libopenh264” : Unspecified error错误,
则需要根据提示下载对应版本的dll库,放到.exe的运行目录下即可
[参考博客](https://bbs.csdn.net/topics/618668825)
[openh264库下载网站](https://bbs.csdn.net/topics/618668825)
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/70e04ea160198d304643443830c0653f.png#pic_center)
缺少一个从一个视频扣ROI图到另外一个视频上叠加的案例
实践
[外链图片转存中...(img-SYbwzGeO-1715839182885)]
[外链图片转存中...(img-08fvLyPm-1715839182885)]
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618668825)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**