openCV笔记
一,图像编程入门
图像的读取和写入
调用读取函数imread()
image=cv::imread(“文件路径”);
要用一个变量来接收文件路径 文件路径要使用\\来划分
从文件读入一个图像,解码,然后分配内存
可以通过在后面加入参数改变输入方法
IMREAD_GRAYSCALE灰度图像有的计算机视觉算法是必须使用灰度图像的。在读入图像的同时进行色彩转换,可以提高运行速度并减少 内存使用
IMREAD_COLOR三通道彩色图像当你用 imshow 显示由整数(CV_16U 表
示 16 位无符号整数,CV_32S 表示 32 位有符号整 数)构成的图像时,图像
每个像素的值会被除
以 256,以便能够在 256 级灰度中显示。同样,在 显示由浮点数构成的图
像时,值的范围会被假设为 0.0(显示黑色)~1.0(显示白
色)。超出这个 范围的值会显示为白色(大于 1.0 的值)或黑色(小于 0.0 的值)
IMREAD_UNCHANGED透明通道
IMREAD_ANYCOLOR任意深度任意颜色
代码示例
//默认读取
image = cv::imread("D:\\学习\\openCV\\open代码\\openCV1\\Screenshot_20221116_095322.jpg");
//读入一个图像文件并将其转换为灰度图像
image = cv::imread("C:\\Users\\y'z\\Desktop\\素材\\预科\\Screenshot_20220731_220411.jpg", IMREAD_GRAYSCALE);
// 读取图像,并将其转换为三通道彩色图像
image = cv::imread("C:\\Users\\y'z\\Desktop\\素材\\预科\\Screenshot_20220731_220411.jpg", IMREAD_COLOR);
if语句
判断图像是否写入成功
如果没有分配图像数据,empty 方法将返回 true。
代码示例
if (image.empty())//先检查一下图像的读取是否正确(如果找不到文件、文件被破坏或者文件格式无法识别,就会发生错误)
{
printf("could not load image...\n");
return -1;
}
定义窗口函数namedWindow()
namedWindow("Original Image", WINDOW_NORMAL);
第一个参数窗口名字,第二个参数为窗口尺寸
(1)WINDOW_AUTOSIZE:窗口以图像实际大小显示(默认)
(2)WINDOW_FREERATIO:自由的比例,且可以调整大小
(2)WINDOW_NORMAL:窗口以合适大小显示,且可以调整大小
(3)WINDOW_OPENGL:创建支持OpenGL,需要有OpenGL支持
定义用来显示图像的窗口,然后让图像在指定的窗口中显示出来
输出图片函数imshow()
cv::imshow("Original Image", image);
输出图片第一个变量为窗口名字,第二个为输出图片变量
图片显示时间函数waitKey()
waitKey(0);//(有返回值)
waitKey(x);这个函数是在一个给定的时间内(单位ms)等待用户按键触发;如果
用户没有按下 键,则接续等待(循环)
当x>0,waitkey返回在x时间内按下的按键的ASCII值,否则返回-1;
当x=0,waitkey表示永久等待,直到有键按下;
如果设置waitKey(0),则表示程序会无限制的等待用户的按键事件
图像色彩空间转换
1.色彩空间转换函数- cvtColor
COLOR_BGR2GRAY= 6彩色到灰度
COLOR_GRAY2BGR= 8灰度到彩色
COLOR_BGR2HSV = 40 BGR到HSV
COLOR_HSV2BGR = 54 HSV到BGR
2.图像保存– imwrite
第一个参数是图像保存路径
第二个参数是图像内存对象
代码示例
Mat gray, hsv;
cvtColor(image, gray, COLOR_BGR2GRAY);
cvtColor(image, hsv, COLOR_BGR2HSV);
cv::namedWindow("HSV", WINDOW_FREERATIO);
cv::namedWindow("灰度", WINDOW_FREERATIO);
imshow("HSV", hsv);
imshow("灰度", gray);
imwrite("D:\\学习\\hsv.png", hsv);
OpenCV中图像对象创建与赋值
C++中Mat对象与创建
代码图示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gPRSNwQ7-1676033202520)(C:\Users\y’z\Desktop\openCV\屏幕截图 2022-12-24 220018.png)]
如果是简单的赋值操作值
如Mat m3=m1
则改变m3,m1也会发生改变
但拷贝会产生一个新的图像即m3.copyTo(m1)
则改变m3,m1不发生改变
ones会将第一个通道变为1
OpenCv中图像像素读写操作
C++中的像素遍历与访问
数组遍历
指针方式遍历(更加快速)
at()函数
单通道图像
对于单通道图像"picture",picture.at(i,j)就表示在第i行第j列的像素值。
即读取了位于(i,j)的像素值
多通道图像
对于多通道图像如RGB图像"picture",可以用picture.at(i,j)[c]来表示某个通道中在(i,j)位置的像素值。
(1)上面的"x"为doubler、Vec3b表示图像元素的类型
(2)(i,j)当然就是指像素点的位置,表示第i行第j列
(3)[c]表示的是通道,对于RGB图像而言,c取0就是B分量;c取1就是G分量;c取2就是R分量(要注意在OpenCV中是按BGR的顺序表示的)
代码示例
void QuickDemo::pixcel_visit_dome(Mat& image)
{
int w = image.cols;//定义列数
int h = image.rows;//定义行数
int dim = image.channels();
for (int row = 0; row < h; row++)//遍历数组
{
for (int col = 0; col < w; col++)
{
if (dim == 1)//灰度图片
{
int pv = image.at<uchar>(row, col);
//cv::Mat 的at(x,y)可以访问元素,在编译时必须明确方法返回值的的类型。
image.at<uchar>(row, col) = 255 - pv;//即对当前色彩取反
}
if (dim == 3)//三通道图片
{
Vec3b bgr = image.at<Vec3b>(row, col);
image.at<Vec3b>(row, col)[0] = 255 - bgr[0];//改变B通道
image.at<Vec3b>(row, col)[1] = 255 - bgr[1];//改变G通道
image.at<Vec3b>(row, col)[2] = 255 - bgr[2];//改变R通道
}
}
}
for (int row = 0; row < h; row++)//遍历指针访问
{
uchar* current_row = image.ptr<uchar>(row);//第row行第一个元素
for (int col = 0; col < w; col++)
{
if (dim == 1)
{
*current_row++ = 255 - *current_row;//即对当前色彩取反
//改变完第一个元素后进行加一即改变为第二个元素
//有多少col就加多少次,直到改变完这一行
}
if (dim == 3)
{
*current_row++ = 255 - *current_row;//改变B通道
*current_row++ = 255 - *current_row;//改变G通道
*current_row++ = 255 - *current_row;//改变R通道
}
}
}
cv::namedWindow("显示读写演示", WINDOW_FREERATIO);
imshow("显示读写演示",image);
}
OpenCV中像素算术操作
像素算术操作
加法add、减法subtract、乘法multiply、除法divide
saturate_cast<类型>(参数1,算术运算符,参数2)函数
可以是无论什么互相运算最小值不小于0不大于255
**注意点:**图像的数据类型、通道数目、大小必须相同
代码示例
void QuickDemo::openators_dome(Mat& image)
{
Mat dst=Mat::zeros(image.size(), image.type());
//因为图像的数据类型、通道数目、大小必须相同,故定义一个全黑的图片大小和传入的图片相同
Mat m = Mat::zeros(image.size(), image.type());
m = Scalar(50,50,50);
//加法
dd(image, m, dst);
//加法基础底层
int w = image.cols;//定义列数
int h = image.rows;//定义行数
for (int row = 0; row < h; row++)//遍历指针访问
{
for (int col = 0; col < w; col++)
{
Vec3b p1 = image.at<Vec3b>(row, col);
Vec3b p2 = m.at<Vec3b>(row, col);
dst.at<Vec3b>(row, col)[0] = saturate_cast<uchar>(p1[0] + p2[0]);//改变B通道
dst.at<Vec3b>(row, col)[1] = saturate_cast<uchar>(p1[1] + p2[1]);//改变G通道
dst.at<Vec3b>(row, col)[2] = saturate_cast<uchar>(p1[2] + p2[2]);//改变R通道
}
}
//减法
subtract(image, m, dst);
//乘法
multiply(image, m, dst);
//除法
divide(image, m, dst);
cv::namedWindow("算术演示", WINDOW_FREERATIO);
imshow("算术演示", dst);
}
滚动条–图像亮度与对比度调节
对比度即令亮的地方跟亮,显示出对比
createTrackbar()
createTrackbar()函数用于创建一个可以调整数值的滑动条(常常也称轨迹
条),并将滑动条附加到指定的窗口上。它往往会和一个回调函数配合起来使
用。
函数原型
int createTrackbar(conststring& trackbarname, conststring& winname, int* value, int count, TrackbarCallback onChange=0, void* userdata=0);
参数说明图示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p7WdUTm8-1676033202521)(C:\Users\y’z\Desktop\openCV\屏幕截图 2022-12-25 183358.png)]
getTrackbarPos()函数
getTrackbarPos()函数用来获取当前轨迹条的位置,配合createTrackbar()函
数使用。
函数原型
int getTrackbarPos(conststring& trackbarname, conststring& winname);
参数说明
第一个参数:conststring& trackbarname,表示轨迹条的名字
第二个参数:conststring& winname,表示轨迹条依托窗口的名称
addWeighted()函数
调整亮度,对比度,我们也可以通过addWeighted实现
共7个参数
第1个参数 第一个输入 src1
第2个参数 第一个输入的权重 alpha
第3个参数 第二个输入 src2
第4个参数 第二个输入的权重 beta
第5个参数 每个数要增加的标量 gamma
第6个参数 输出 dst
第7个参数 输出的可选深度(一般用不到)
公式
dst = src1 * alpha + src2 * beta + gamma
范围截断在[0,255]
提高亮度 add(src, m, dst);
降低亮度 subtract(src, m, dst);
代码示例
Mat src, dst, m;
static void on_light(int lightness, void* userdata)
{
Mat dst = Mat::zeros(src.size(), src.type());
Mat m = Mat::zeros(src.size(), src.type());
double light = lightness - 50;
//在外视图为50时内置数值为0即原图
/*
gamma范围变成[-50,50]
这样我们就实现了增大/降低亮度,
先 降低一半,不然只能提高
*/
addWeighted(src, 1.0, m, 0, light, dst);
/*
由公式:dst = src1 * alpha + src2 * beta + gamma 可知
设置第1个权重1,第二个权重0
相当于图片融合时,只有第1个的成分
设置要增加的标量为lightness
可以实现亮度的调整
*/
imshow("亮度与对比度调节", dst);
}
static void on_contrast(int contrast, void* userdata)
{
Mat dst = Mat::zeros(src.size(), src.type());
Mat m = Mat::zeros(src.size(), src.type());
double contra = contrast / 100.0;
//注意要/100.0而不是/100
//因为除法会产生精度丢失
/*
这里的contrast初始值1 是一个[0,2]范围的数, 实现 可以调高调小
*/
addWeighted(src, contra, m, 0.0, 0, dst);
/*
由公式:dst = src1 * alpha + src2 * beta + gamma 可知
设置第1个权重cotrast,第二个权重0
相当于图片融合时,只有第1个的成分
同时可以实现对比度的调整
*/
imshow("亮度与对比度调节", dst);
}
void QuickDemo::tracking_ber_demo(Mat& image)
{
namedWindow("亮度与对比度调节", WINDOW_FREERATIO);
src = image;
int lightness = 50; //当前值
int max_light = 100;//最大值
int contrast = 100; //当前值
int max_contrast = 200;//最大值
createTrackbar("Value_light:", "亮度与对比度调节", &lightness, max_light, on_light, (void*)(&image));
createTrackbar("Value Bar:", "亮度与对比度调节", &contrast, max_contrast, on_contrast, (void*)(&image));
}
键盘响应操作
使用waitKey(x)函数来输入判断值(有返回值)
当x>0,waitkey返回在x时间内按下的按键的ASCII值,否则返回-1;
当x=0,waitkey表示永久等待,直到有键按下;
代码示例
void QuickDemo::key_demo(Mat& image)
{
Mat dst = Mat::zeros(image.size(), image.type());
while(true)
{
int c = waitKey(100);
if (c == 27)//退出Esc键int类型数值为27
{
break;
}
if (c == 49)//键盘上“1”的int类型数值为49
{
cout << "you enter key #1" << endl;
cvtColor(image, dst, COLOR_BGR2GRAY);
}
if (c == 50)//键盘上“2”的int类型数值为50
{
cout << "you enter key #2" << endl;
cvtColor(image, dst, COLOR_BGR2HSV);
}
if (c == 51)//键盘上“3”的int类型数值为51
{
cout << "you enter key #3" << endl;
dst = Scalar(50,50,50);
add(image, dst, dst);
}
namedWindow("键盘操作图像", WINDOW_FREERATIO);
imshow("键盘操作图像", dst);
}
}
openCV自带颜色表操作
Look Up Table(LUT)查找表
解释了LUT查找表的作用与用法,代码实现与API介绍
applyColorMap(src,dst, COLORMAP)
src表示输入图像
dst表示输出图像
匹配到的颜色LUT,OpenCV支持13种颜色风格的查找表映射
首先把颜色表做成一个由openCV提供的枚举数组
int colormap[] = {
COLORMAP_AUTUMN,
COLORMAP_BONE,
COLORMAP_JET,
COLORMAP_WINTER,
COLORMAP_RAINBOW,
COLORMAP_OCEAN,
COLORMAP_SUMMER,
COLORMAP_SPRING,
COLORMAP_COOL,
COLORMAP_HSV,//10
COLORMAP_PINK,
COLORMAP_HOT,
COLORMAP_PARULA,
COLORMAP_MAGMA,
COLORMAP_INFERNO,
COLORMAP_PLASMA,
COLORMAP_VIRIDIS,
COLORMAP_CIVIDIS,
COLORMAP_TWILIGHT,
COLORMAP_TWILIGHT_SHIFTED,//20
COLORMAP_TURBO,
COLORMAP_DEEPGREEN
};
代码示例
void QuickDemo::color_style_demo(Mat& image)
{
int colormap[] = {
COLORMAP_AUTUMN,
COLORMAP_BONE,
COLORMAP_JET,
COLORMAP_WINTER,
COLORMAP_RAINBOW,
COLORMAP_OCEAN,
COLORMAP_SUMMER,
COLORMAP_SPRING,
COLORMAP_COOL,
COLORMAP_HSV,//10
COLORMAP_PINK,
COLORMAP_HOT,
COLORMAP_PARULA,
COLORMAP_MAGMA,
COLORMAP_INFERNO,
COLORMAP_PLASMA,
COLORMAP_VIRIDIS,
COLORMAP_CIVIDIS,
COLORMAP_TWILIGHT,
COLORMAP_TWILIGHT_SHIFTED,//20
COLORMAP_TURBO,
COLORMAP_DEEPGREEN
};
Mat dst= Mat::zeros(image.size(), image.type());
int index = 0;
while (true)
{
int c = waitKey(500);
if (c == 27)//退出Esc键int类型数值为27
{
break;
}
applyColorMap(image, dst, colormap[index % 22]);
index++;
namedWindow("色彩图像", WINDOW_FREERATIO);
imshow("色彩图像", dst);
}
}
像素操作之逻辑操作
bitwise_and 且
bitwise_xor 异或,即并且后取反
bitwise_or 或
上面三个类似,都是针对两张图像的位操作
bitwise_not
针对输入图像,图像取反操作,二值图像分析中经常用
rectangle()函数第一种形式
void cv::rectangle ( InputOutputArray img,
Point pt1,
Point pt2,
const Scalar & color,
int thickness = 1,
int lineType = LINE_8,
int shift = 0
)
参数说明
img 图像。
pt1 矩形的顶点。
pt2 与 pt1 相对的矩形的顶点。意思是pt1和pt2是对角顶点
color 颜色或亮度(灰度图像)。
thickness 构成矩形的线条的厚度。负值,如 FILLED、-1,意味着函数必须
绘制一个填充的矩形。
lineType 线的类型。
shift 点坐标中的小数位数,一般取0,因为像素一般都是整型值。
rectangle()函数第二种形式
void cv::rectangle ( Mat & img,
Rect rec,
const Scalar & color,
int thickness = 1,
int lineType = LINE_8,
int shift = 0
)
基本参数和rectangle()函数第一种一致
其中第二个参数Rect(rec)代表矩形边框
Rect()函数
基本概念:
Rect(int x, int y, int width, int height);
参数含义:
Rect(左上角x坐标 , 左上角y坐标,矩形的宽,矩形的高)
图示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6LlO34aC-1676033202522)(C:\Users\y’z\Desktop\openCV\屏幕截图 2022-12-26 184528.png)]
代码示例
void QuickDemo::bitwise_demo(Mat& image)
{
Mat m1 = Mat::zeros(Size(256, 256), CV_8UC3);
Mat m2 = Mat::zeros(Size(256, 256), CV_8UC3);
rectangle(m2, Rect(100, 100, 80, 80), Scalar(0, 255,255), 25, LINE_8, 0);
rectangle(m1, Rect(150, 150, 80, 80), Scalar(255, 255,0), -1, LINE_8, 0);
imshow("m1", m1);
imshow("m2", m2);
Mat dst;
bitwise_xor(m1, m2, dst);
imshow("像素位操作", dst);
}
通道分离与合并
openCV中默认imread函数加载图像文件,加载进来的是三通道彩色图像,色
彩空间是RGB色彩空间、通道顺序是BGR(蓝色、绿色、红色)、对于通道的图
像OpenCV中提供了两个API函数用以实现通道分离与合并。
split()函数
split(参数1,参数2); //通道分类
参数一:输入图像
参数二:分离图像数组
彩色图像一般是三通道图像(24位),一个像素需要3个值来表示分别是0-
255,例如(255,178,233)你单通道(8位)只能表示一个值,范围0-255,
所以都是灰度图
merge()函数
merge (参数1,参数2); //通道合并
参数一:输入图像数组
参数二:输出图像
mixChannels()函数
代码参数
void mixChannels(
const Mat* src, //输入数组或向量矩阵,所有矩阵的大小和深度必须相同。
size_t nsrcs, //矩阵的数量
Mat* dst, //输出数组或矩阵向量,大小和深度必须与src[0]相同
size_t ndsts,//矩阵的数量
const int* fromTo,//指定被复制通道与要复制到的位置组成的索引对
size_t npairs //fromTo中索引对的数目即通道数
);
将一个4通道BGRA图像分割成一个3通道BGR和一个单独的alpha通道图像:
其中,索引对from_to[] = { 0, 2, 1, 1, 2, 0, 3, 3 }的含义为:
bgra的0通道复制到out[]的2通道,即bgr的0通道;
bgra的1通道复制到out[]的1通道,即bgr的1通道;
bgra的2通道复制到out[]的0通道,即bgr的2通道;
bgra的3通道复制到out[]的3通道,即alpha通道;
图示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j8S0gR9l-1676033202522)(C:\Users\y’z\Desktop\openCV\屏幕截图 2022-12-26 212220.png)]
通道分离与合并代码示例
void QuickDemo::channels_demo(Mat& image)
{
std::vector<Mat> mv;
split(image, mv);//通道分离
namedWindow("蓝色", WINDOW_FREERATIO);
namedWindow("绿色", WINDOW_FREERATIO);
namedWindow("红色", WINDOW_FREERATIO);
imshow("蓝色", mv[0]);
imshow("绿色", mv[1]);
imshow("红色", mv[2]);
Mat dst= Mat::zeros(image.size(), image.type());
mv[0] = 0;
mv[1] = 0;
mv[2] = 0;
//彩色图像一般是三通道图像(24位),一个像素需要3个值来表示分别是0-255
//单通道(8位)只能表示一个值,范围0-255,所以都是灰度图
//故要将其他某个通道改为0在合并为一个图像才为彩色图
merge(mv, dst);
namedWindow("混合", WINDOW_FREERATIO);
imshow("混合", dst);//图像合并
int from_to[] = { 0,2,1,1,2,0 };
mixChannels(&image, 1, &dst, 1, from_to, 3);//图像合并
namedWindow("通道混合", WINDOW_FREERATIO);
imshow("通道混合", dst);
}
色彩空间与色彩空间转换
RGB色彩空间
HSV色彩空问
YUV色彩空间
YCrCb色彩空间
色彩空间转换cvtColor()函数
cvtColor( src, #输入
dst=None[, #输出
code[, #色彩空间转换的代码或表示.
dstCn=None]]#目标图像通道数
);
提取指定色彩范围区域inRange()函数
代码示例
inRange(hsv, Scalar(0,0,221), Scalar(180,30,255), mask);
第一个参数,输入图片
第二个参数,颜色图表目标颜色的最小值
第三个参数,颜色图表目标颜色的最大值
第四个参数,输出图片
颜色图表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nbhJtsm4-1676033202523)(C:\Users\y’z\Desktop\openCV\屏幕截图 2022-12-27 130948.png)]
copyTo()函数
函数原型
void copyTo( OutputArray m ) const;
void copyTo( OutputArray m, InputArray mask ) const;
- 一个参数:一张输出图像
- 两个参数:一张输出图像和一个掩码图
image_in.copyTo(image_out);
就是把image_in这张图复制(copy to)到image_out上。
image_in.copyTo(image_out,mask);
则是不仅把image_in这张图复制(copy to)到image_out上,且image_in对应mask中像素值为0的像素点都不会贴到image_out上。
代码示例
int main()
{
// 打开图像
cv::Mat image = cv::imread("C:\\Users\\y'z\\Desktop\\红蓝灯条.bmp", 1);
if (image.empty())//先检查一下图像的读取是否正确(如果找不到文件、文件被破坏或者文件格式无法识别,就会发生错误)
{
printf("could not load image...\n");
return -1;
}
cv::namedWindow("Image", WINDOW_FREERATIO);
Mat hsv;
cvtColor(image, hsv, COLOR_BGR2HSV);
Mat mask;
inRange(hsv, Scalar(0,0,221), Scalar(180,30,255), mask);
cv::namedWindow("mask", WINDOW_FREERATIO);
cv::namedWindow("redback", WINDOW_FREERATIO);
Mat redback = Mat::zeros(image.size(), image.type());
redback = Scalar(0, 0, 255);
bitwise_not(mask, mask);
image.copyTo(redback, mask);
cv::imshow("Image", image);
cv::imshow("mask", mask);
cv::imshow("redback", redback);
waitKey(0);
}
图像像素值统计
最小(min)
最大(max)
均值(mean)
标准方差(standard deviation)
最大最小值minMaxLoc()函数
void minMaxLoc( const Mat& src,
double* minVal,
double* maxVal=0,
Point* minLoc=0,
Point* maxLoc=0,
const Mat& mask=Mat()
);
参数1:src 输入单通道矩阵.
参数2:minVal 返回最小值的指针; 如果不需要输入NULL.
参数3:maxVal 返回最大值的指针; 如果不需要输入NULL.
参数4:minLoc 返回最小值位置的指针 (二维情况下); 如果不需要输入NULL.
参数5:maxLoc 返回最大值位置的指针 (二维情况下); 如果不需要输入NULL.
参数6:mask 可选参数,用于选择一个子矩阵.
计算均值与标准方差meanStdDev()函数
void meanStdDev(InputArray src,OutputArray mean, OutputArray stddev, InputArray mask=noArray())
src:输入矩阵,这个矩阵应该是1-4通道的,这可以将计算结果存在Scalar_ ‘s中
mean:输出参数,计算均值
stddev:输出参数,计算标准差
mask:可选参数
代码示例
双精度型变量 minv和maxv;指针变量minLoc,maxLoc。
因为图片是多通道的,所以使用一个容器装取数值,并且用split分离图片到MV
中
通过for循环操作,遍历图片信息,并且打印信息到终端
如果图像有平均值但是方差为0则有可能为纯色图片或无信息包含
void QuickDemo::pixel_statistic_demo(Mat &image)
{
double minv, maxv;//定义最值
Point minLoc, maxLoc;//定义最值地址
std::vector<Mat>mv;//mv是一个Mat类型的容器 装在这个容器内
split(image, mv);
for (int i = 0; i < mv.size(); i++)
{
//分别打印各个通道的数值
minMaxLoc(mv[i], &minv, &maxv, &minLoc, &maxLoc, Mat());//求出图像的最大值和最小值。
std::cout <<"No.channels:"<<i<<"minvalue:" << minv << "maxvalue:" << maxv << std::endl;
}
Mat mean, stddev;
meanStdDev(image, mean, stddev);//求出图像的均值和方差
std::cout << "mean:" << mean << std::endl;
std::cout << "stddev:" << stddev << std::endl;
}
图像几何形状绘制
- 学会使用 cv.line 绘制一条线;
- 学会使用 cv.circle 绘制圆;
- 学会使用 cv.rectangle 绘矩形;
- 学会使用 cv.ellipse 绘椭圆。
cv.line() 函数使用
代码示例
cv.line(img, pt1, pt2, color[, thickness=1, lineType=LINE_8, shift=0]) → img
参数说明
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DJm8U5q6-1676033202523)(C:\Users\y’z\Desktop\openCV\屏幕截图 2022-12-27 181024.png)]
注意事项
1,绘图操作会直接对传入的图像 img 进行修改,是否接受函数返回值都可以。
2,在绘制直线时不能选择填充线型 cv.FILLED ,否则会报错。
3,图像尺寸较小时,LINE_4 线型存在明显的锯齿,LINE_AA 线型更加平滑。
4,图像尺寸较大时,则线型的影响并不大,推荐采用默认值 LINE_8。
5,如果设置了 thickness,关键词 lineType 可以省略;如果没有设置
thickness,则关键词 lineType 不能省略,否则输入的线型参数会被错误地解
释为线宽。
cv.circle() 函数使用
代码示例
cv.circle(img, center, radius, color[, thickness=1, lineType=LINE_8, shift=0]) → img
参数说明
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qlqvd2h5-1676033202523)(C:\Users\y’z\Desktop\openCV\屏幕截图 2022-12-27 181436.png)]
注意事项
1,在单通道的灰度图像上只能绘制灰度线条,不能绘制彩色线条 。但是,线
条颜色 color 可以是标量 b,也可以是元组 (b,g,r),都会被解释为灰度值 b。元
组中的后两个通道的参数是无效的。
2,在绘制圆形时不能选择填充线型 cv.FILLED,无效。
cv.rectangle() 函数使用
代码示例
cv.rectangle(img, pt1, pt2, color[, thickness=1, lineType=LINE_8, shift=0]) → img
有第二种方式详见像素操作之逻辑操作
参数说明
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y9C63qzW-1676033202524)(C:\Users\y’z\Desktop\openCV\屏幕截图 2022-12-27 181747.png)]
注意事项
1,使用rec参数绘制矩形,r.tl() 和 r.br() 是矩形的对角点。
cv.ellipse() 函数使用
代码示例
cv.ellipse(img, center, axes, angle, startAngle, endAngle, color[, thickness=1, lineType=LINE_8, shift=0]) → img
参数说明
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8epTRxI2-1676033202524)(C:\Users\y’z\Desktop\openCV\屏幕截图 2022-12-27 182201.png)]
代码示例
void QuickDemo::drawing_demo(Mat& image)
{
Rect rect;
rect.x = 100;
rect.y = 100;
rect.height = 250;
rect.width = 300;
Mat bg = Mat::zeros(image.size(), image.type());
rectangle(bg, rect, Scalar(0, 0, 255), -1, 8, 0);
circle(bg, Point(350, 400), 15, Scalar(255, 0, 0), -1, 8, 0);
line(bg, Point(100, 100), Point(350, 400), Scalar(0, 255, 0), 4, LINE_AA, 0);
RotatedRect rrt;
rrt.center = Point(200, 200);
rrt.size = Size(100, 200);
rrt.angle = 0.0;
ellipse(image, rrt, Scalar(0, 255, 255), 2, 8);
Mat dst;
addWeighted(image, 0.7, bg, 0.3, 0, dst);
imshow("绘制图片", dst);
}
随机数与随机颜色
RNG类
RNG类是opencv里C++的随机数产生器。它可产生一个64位的int随机数。
计算机产生的随机数都是伪随机数,是根据种子seed和特定算法计算出来的。
所以,只要种子一定,算法一定,产生的随机数是相同的要想产生完全重复的
随机数,可以用系统时间做种子
产生一个随机数RNG可以产生3种随机数
RNG(int seed):使用种子seed产生一个64位随机整数,默认-1
RNG::uniform( ) :产生一个均匀分布的随机数
RNG::uniform(a, b ) 返回一个[a,b)范围的均匀分布的随机数,a,b的数据类型要
一致,而且必须是int、float、double中的一种,默认是int。
RNG::gaussian( ) : 产生一个高斯分布的随机数
RNG::gaussian( σ) 返回一个均值为0,标准差为σ的随机数。
代码示例
Mat canvas = Mat::zeros(Size(512, 512), 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);
int y1 = rng.uniform(0, h);
int x2 = rng.uniform(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);
imshow("随机绘制演示", canvas);
}
}
多边形填充与绘制
polylines(预定义的背景画布, 点数组, true, 颜色, 线宽, 线形, shift参数);
如果直接如Rect等API直接将线宽参数赋值-1,在执行程序会报错,
此处有专门的填充函数来实现:
fillPoly(定义的背景画布, 点数组, 颜色, 线形, shift参数);
void QuickDemo::polyline_demo()
{
//创建画布
Mat canvas = Mat::zeros(Size(512, 512), CV_8UC3);
Point p1(100, 100);
Point p2(350, 100);
Point p3(450, 280);
Point p4(320, 450);
Point p5(80, 400);
//生成点数组向量,并初始化容量,使用索引赋值,如果不初始化容量,只能通过push_back()来填充元素。
vector<Point> pts;
pts.push_back(p1);
pts.push_back(p2);
pts.push_back(p3);
pts.push_back(p4);
pts.push_back(p5);
//绘制多边形polylines()
polylines(canvas, pts, true, Scalar(255, 0, 0), 3, 8, 0);
fillPoly(canvas, pts, Scalar(0, 255, 0), 8, 0);
imshow("多边形绘制", canvas);
}
也可利用一个函数完成绘制与填充
drawContours(画布, 包含多个点数组的数组, 点数组索引, 颜色, 线宽);
代码示例
void QuickDemo::polyline_demo()
{
//创建画布
Mat canvas = Mat::zeros(Size(512, 512), CV_8UC3);
Point p1(100, 100);
Point p2(350, 100);
Point p3(450, 280);
Point p4(320, 450);
Point p5(80, 400);
//生成点数组向量,并初始化容量,使用索引赋值,如果不初始化容量,只能通过push_back()来填充元素。
vector<Point> pts;
pts.push_back(p1);
pts.push_back(p2);
pts.push_back(p3);
pts.push_back(p4);
pts.push_back(p5);
//创建包含多个点数组的数组
vector<vector<Point>> contours;
contours.push_back(pts);
drawContours(canvas, contours, -1, Scalar(255, 0, 0), -1);
imshow("多边形绘制", canvas);
}
openCV鼠标操作与响应
openCV支持鼠标事件操作,通过setMouseCallback函数来设置鼠标事件的回调函数,从而使得每次操作鼠标之后就会调用对应的回调函数。
鼠标响应图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s3wO4X6x-1676033202524)(C:\Users\y’z\Desktop\openCV\屏幕截图 2022-12-28 122314.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fPm85Sbb-1676033202525)(C:\Users\y’z\Desktop\openCV\屏幕截图 2022-12-28 122409.png)]
setMouseCallback函数函数原型
void setMousecallback(const string& winname, MouseCallback onMouse, void* userdata=0);
//winname:窗口的名字
//onMouse:鼠标响应函数,回调函数。指定窗口里每次鼠标时间发生的时候,被调用的函数指针。
//这个函数的原型应该为void on_Mouse(int event, int x, int y, int flags, void* param);
//userdate:传给回调函数的参数
//回调函数需要一个void*类型的参数,而如果想要将Mat对象传递进去,就需要一个数据类型的转换,在传递进去以后,再将这个参数转换回Mat类型
回调函数函数原型
void on_Mouse(int event, int x, int y, int flags, void* param);
//event是 CV_EVENT_*变量之一
//x和y是鼠标指针在图像坐标系的坐标(不是窗口坐标系)
//flags是CV_EVENT_FLAG的组合, param是用户定义的传递到setMouseCallback函数调用的参数。
代码示例
Point sp(-1, -1);//鼠标的开始的位置初始化起始点,(-1,-1)
Point ep(-1, -1);//初始化终结点,(-1,-1)
Mat temp;
static void on_draw(int event, int x, int y, int flags, void* userdata)
{
//回调函数需要一个void*类型的参数,而如果想要将Mat对象传递进去,就需要一个数据类型的转换,在传递进去以后,再将这个参数转换回Mat类型,具体操作如下
Mat image = *((Mat*)userdata);
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);
temp.copyTo(image);
imshow("ROI区域", image(box));
rectangle(image, box, Scalar(0, 0, 255), 2, 8, 0);
imshow("鼠标绘制", image);
sp.x = -1;
sp.y = -1;//复位,为下一次做准备
}
}
else if (event == EVENT_MOUSEMOVE)//鼠标移动。把绘画过程展示出来
{
if (sp.x > 0 && sp.y > 0)//必须是鼠标按下之后(sp.x,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);
temp.copyTo(image);//擦除移动时的涂鸦
rectangle(image, box, Scalar(0, 0, 255), 2, 8, 0);
imshow("鼠标绘制", image);
}
}
}
}
void QuickDemo::mouse_drawing_demo(Mat& image)
{
namedWindow("鼠标绘制", WINDOW_AUTOSIZE);
setMouseCallback("鼠标绘制", on_draw, (void*)(&image));
//设置窗口的回调函数。参数1表示名称,参数2表示调用on_draw
//参数3传给回调函数的参数 进行了类型转换将Mat改为void故再回调函数中要改回
//void* a = (void*)(&image);(void*)(&image);
imshow("鼠标绘制", image);
temp = image.clone();
}
图像像素类型与归一化
什么是归一化
概念一:归一化是把需要处理的数据通过某种算法处理后限制在所需要的一定范
围内。
概念二:归一化是指在处理数据的过程中,把数据范围相差较大的数据进行标准
化处理,让所有的数据都处于同一个数量级中。
为什么要归一化
首先,归一化是 为了后面数据处理的方便,其次是保证程序运行时收敛加快。
归一化的具体作用是归纳统一样本的统计分布性。归一化在0-1之间是统计的概
率分布,归一化在某个区间上是统计的坐标分布。
归一化的目的,是使得没有可比性的数据变得具有可比性,同时又保持相比较的
两个数据之间的相对关系,如大小关系;或是为了作图,原来很难在一张图上作
出来,归一化后就可以很方便的给出图上的相对位置等。
OpenCV中提供了四种归一化的方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Kw7iTHOG-1676033202525)(C:\Users\y’z\Desktop\openCV\1aaa60e7f9494d04b4d5c886e3133143.png)]
最常用的就是 NORM_MINMAX 归一化方法。
归一化数据函数normalize()
void normalize(InputArray src,
OutputArray dst,
double alpha = 1,
double beta = 0,
intnorm_type = NORM_L2,
int dtype = -1,
InputArray mask = noArray()
);
src:输入图像/数组
dst:输出图像/数组
alpha:范围的最小值
beta:范围的最大值(不用于范数归一化)
intnorm_type:归一操作的类型,有如下三种:
NORM_MINMAX:将数组的数值归一化到[alpha,beta]内,常用。
NORM_L1:归一化数组的L1-范数(绝对值的和)
NORM_L2:归一化数组的(欧几里德)L2-范数
dtype:为负数时,输出数组的类型与输入数组相同,否则只是通道数相同,类型默认为:type = CV_MAT_DEPTH//默认类型与src一致
mask:指定操作的区域/空间mask = noArray()// mask默认值为空
convertTo 函数
函数原型
void convertTo( OutputArray m, int rtype, double alpha=1, double beta=0 ) const;
参数说明
m – 目标矩阵。如果m在运算前没有合适的尺寸或类型,将被重新分配。
rtype – 目标矩阵的类型。因为目标矩阵的通道数与源矩阵一样,所以rtype也可以看做是目标矩阵的位深度。如果rtype为负值,目标矩阵和源矩阵将使用同样的类型。
alpha – 尺度变换因子(可选)。
beta – 附加到尺度变换后的值上的偏移量(可选)。
代码示例
void QuickDemo::norm_demo(Mat& image) {
Mat dst;
std::cout << image.type() << std::endl;//16 CV_8UC3 八位3通道
image.convertTo(image, CV_32F);//转化成CV_32FC3类型 单通道,每通道都是32位浮点数
// CV_8UC3, CV_32FC3
std::cout << image.type() << std::endl;//21
normalize(image, dst, 1.0, 0, NORM_MINMAX);//变成浮点数类型后,一定要归一化,不然显示有问题
//转回去,转CV_8UC3
//image.convertTo(image, CV_8UC3);
std::cout << image.type() << std::endl;
std::cout << dst.type() << std::endl;//21
imshow("图像数据归一化", dst);
}
图像放缩与插值
图像插值(Image Interpolation)最常见四种插值算法
INTER_NEAREST = 0
INTER_LINEAR = 1
INTER_CUBIC = 2
INTER_LANCZOS4=4
相关的应用场景
几何变换、透视变换、插值计算新像素
如果size有值,使用size做放缩插值,否则根据fx与fy
OpenCV中提供的 resize 函数可以实现图像大小变换,需要用到插值方法。
resize函数
void resize( InputArray src, OutputArray dst,
Size dsize, double fx = 0, double fy = 0,
int interpolation = INTER_LINEAR );
参数:
src -- 输入图像
dst -- 输出图像,它有大小dsize(当它不为零时)或
通过src.size(),fx和fy计算的大小;
dst的类型与src相同。
dsize -- 输出图像的大小,dsize或fx和fy必须非零 (两者必须有一个非零)
fx -- 沿水平轴的比例因子,When it is 0, it is computed as (double)dsize.width/src.cols
fy -- 沿垂直轴的比例因子,When it is 0, it is computed as (double)dsize.height/src.rows
interpolation -- 插值方法,默认插值方法为 INTER_LINEAR
有四种常用的插值方法:
INTER_NEAREST = 0 (最临近插值算法)
INTER_LINEAR = 1 (双线性插值算法)
INTER_CUBIC = 2 (4x4像素邻域的双三次插值)
INTER_LANCZOS4 = 4 (8x8像素邻域的Lanczos插值)
代码示例
void QuickDemo::resize_demo(Mat& image)
{
Mat zoomin, zoomout;
int h = image.rows;
int w = image.cols;
resize(image, zoomin, Size(w / 2, h / 2), 0, 0, INTER_LINEAR);
imshow("图像缩放", zoomin);
resize(image, zoomout, Size(w * 1.5, h * 1.5), 0, 0, INTER_LINEAR);//不要放大倍数太大
imshow("图像放大", zoomout);
}
openCV图像翻转与旋转
翻转图像
opencv中使用**filp()**可以实现图像翻转
flip(src,dst=None, flipCode)
参数解释
src:输入图像
dst:输出图像
flipCode:flipCode 一个标志来指定如何翻转数组;0表示上下翻转,正数表示左右翻转,负数表示上下左右都翻转。
代码示例
void QuickDemo::flip_demo(Mat& image)
{
Mat dst;
flip(image, dst, -1);
imshow("图像翻转", dst);
}
旋转图像
getRotationMatrix2D函数
Mat cv::getRotationMatrix2D ( Point2f center,
double angle,
double scale
)
参数说明
center: Point2f类型,表示原图像的旋转中心
angle: double类型,表示图像旋转角度,角度为正则表示逆时针旋转,角度为
负表示逆时针旋转(坐标原点是图像左上角)
scale: 缩放系数
注意此函数会返回一个 2*3 矩阵
矩阵后两项为旋转中心
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FzBfb99y-1676033202525)(C:\Users\y’z\Desktop\openCV\屏幕截图 2022-12-29 184624.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FF7gmlU5-1676033202526)(C:\Users\y’z\Desktop\openCV\屏幕截图 2022-12-29 205728.png)]
warpAffine函数
void cv::warpAffine ( InputArray src,
OutputArray dst,
InputArray M,
Size dsize,
int flags = INTER_LINEAR,
int borderMode = BORDER_CONSTANT,
)
参数解释
src: 输入图像
dst: 输出图像,尺寸由dsize指定,图像类型与原图像一致
M: 2X3的变换矩阵
dsize: 指定图像输出尺寸
flags: 插值算法标识符,有默认值INTER_LINEAR
常见的插值算法如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DFyYMeCR-1676033202526)(C:\Users\y’z\Desktop\openCV\屏幕截图 2022-12-29 181016.png)]
并且要通过下图来进行图像处理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v0Zp6cLa-1676033202526)(C:\Users\y’z\Desktop\openCV\屏幕截图 2022-12-29 181638.png)]
通过如上图,我们可以计算新图像的宽度,高度,旋转中心的通式
新宽度nw = wcosΘ + hsinΘ
新宽度nh = wsinΘ + hcosΘ
新旋转中心x +=( nw/2 - w/2)
新旋转中心y +=( nh/2 - h/2)
代码示例
void QuickDemo::rotate_demo(Mat& image) {
Mat dst, M;//M为变换矩阵
int w = image.cols;
int h = image.rows;
M = getRotationMatrix2D(Point2f(w / 2, h / 2), 45, 1.0);
//C++的abs则可以自然支持对整数和浮点数两个版本(实际上还能够支持复数)
double cos = abs(M.at<double>(0, 0));//
double sin = abs(M.at<double>(0, 1));//abs为取绝对值函数
int nw = w * cos + h * sin;
int nh = w * sin + h * cos;
//新图像的旋转中心
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(0,200,0));
imshow("旋转演示", dst);
}
视频文件摄像头使用
virtual bool read(OutputArray image);
功能描述:抓取、解码并返回下一帧视频。 视频帧将从image返回,如果没有帧
被抓取,图像将为空。 返回false,表示如果没有抓取帧
打开摄像头
void QuickDemo::vedio_demo()
{
VideoCapture capture(0);
Mat frame;
while (true)
{
capture.read(frame);
flip(frame, frame, 1);//将镜头旋转为正,否则为镜像
//如果没有抓取到帧,就退出
if (frame.empty())
{
break;
}
imshow("frame", frame);
int c = waitKey(1);
if (c == 27)//按 ESC 退出
{
break;
}
}
//最后一定要release
capture.release();
}
打开视频文件
void QuickDemo::vedio_demo()
{
VideoCapture capture("E://BaiduNetdiskDownload//10__bmp图标显示.mp4");
Mat frame;
while (true)
{
capture.read(frame);
//如果没有抓取到帧,就退出
if (frame.empty())
{
break;
}
imshow("frame",frame);
//每间隔1ms就捕捉一次键盘输入
int c = waitKey(1);
if (c == 27)//按 ESC 退出
{
break;
}
}
//最后一定要release
capture.release();
}
视频处理与保存
视频处理
分辨率
通常国际标准,我们把视频分辨率分为三类
SD—— 标清
HD——高清
UD——超高清
帧
帧相关的属性,具体如下
帧宽度——frame_width
帧高度——frame_height
总帧数——frame_count
帧速率—— FPS(Frames Per Second)
视频属性获取
在opencv中,我们如果要获取视频的属性,就要用到VideoCapture类的一个方法
get
get
返回指定VideoCapture属性
共1个参数
第1个参数 指定的属性
本文只用到4种属性
CAP_PROP_FRAME_WIDTH - 视频流中帧的宽度
CAP_PROP_FRAME_HEIGHT - 视频流中帧的高度
CAP_PROP_FRAME_COUNT - 视频文件中的帧数
CAP_PROP_FPS - 帧率
代码示例
void QuickDemo::video_demo(Mat& image) {
VideoCapture cap("D:/WorkSpace/Opencv/videos/mouse.mp4");//读取视频文件
int frame_width = cap.get(CAP_PROP_FRAME_WIDTH);
int frame_height = cap.get(CAP_PROP_FRAME_HEIGHT);
int frame_count = cap.get(CAP_PROP_FRAME_COUNT);
double fps = cap.get(CAP_PROP_FPS);
std::cout << "frame_width:" << frame_width << std::endl;
std::cout << "frame_height:" << frame_height << std::endl;
std::cout << "frame_count:" << frame_count << std::endl;
std::cout << "fps:" << fps << std::endl;
Mat frame;
while (true) {
cap.read(frame);//frame为输出,read是将捕获到的视频一帧一帧的传入frame
//对视频读取时,同图像一样会有判空操作
if (frame.empty()) {
break;
}
imshow("frame", frame);
int c = waitKey(1);
if (c == 27) {
break;
}
}
cap.release();
}
注意事项
既然有get,那就有对应的set。但是我们使用set会涉及很多的问题
比如上一章中摄像头
当我们使用set对摄像头传来的视频操作时
如果超出了摄像头硬件的范围,即使设置的再好也不会显示
视频保存
在opencv中,关于视频的写操作是通过VideoWriter类来实现的。
方法
通过 VideoWriter创建一个视频写入对象。(创建时,我们要传入各种写入要求)
再通过writer写入。
释放
VideoWriter 类
函数原型
VideoWriter (const String &filename, int fourcc, double fps, Size frameSize, bool isColor=true)
视频写入对象
共5个参数
第1个参数 视频文件路径
第2个参数 视频编码方式(我们可以通过VideoCapture::get(CAP_PROP_FOURCC)获得)即原路径
第3个参数 fps
第4个参数 size
第5个参数 是否为彩色
write
write
写入下一个视频帧
共1个参数
第1个参数 输入的视频帧
注意事项
同VideoCapture类对象一样,VideoWirter对象也要在末尾写上release
代码示例
void QuickDemo::video_demo(Mat& image) {
VideoCapture cap("D:/WorkSpace/Opencv/videos/mouse.mp4");//读取视频文件
int frame_width = cap.get(CAP_PROP_FRAME_WIDTH);
int frame_height = cap.get(CAP_PROP_FRAME_HEIGHT);
int frame_count = cap.get(CAP_PROP_FRAME_COUNT);
double fps = cap.get(CAP_PROP_FPS);
std::cout << "frame_width:" << frame_width << std::endl;
std::cout << "frame_height:" << frame_height << std::endl;
std::cout << "frame_count:" << frame_count << std::endl;
std::cout << "fps:" << fps << std::endl;
VideoWriter wri("D:/WorkSpace/Opencv/videos/wri.mp4", cap.get(CAP_PROP_FOURCC), fps, Size(frame_width, frame_height), true);
Mat frame;
while (true) {
cap.read(frame);//frame为输出,read是将捕获到的视频一帧一帧的传入frame
//对视频读取时,同图像一样会有判空操作
if (frame.empty()) {
break;
}
wri.write(frame);
imshow("frame", frame);
int c = waitKey(1);
if (c == 27) {
break;
}
}
cap.release();
wri.release();
}
注意事项
opencv,只专注于视频画面的处理,没有声音,不处理音频,如果想处理音
频,要涉及其他领域
opencv保存视频是有一定限制的,理论上说单个视频不要超过2G
openCV图像直方图
图像直方图的解释
图像直方图是图像像素值的统计学特征、计算代价较小,具有图像平移、旋转、
缩放不变性等众多优点,广泛地应用于图像处理的各个领域,特别是灰度图像的
阈值分割、基于颜色的图像检索以及图像分类、反向投影跟踪。常见的分为
1,灰度直方图
2,颜色直方图
Bins
Bins是指直方图的大小范围,对于像素值取值在0~255之间的,最少有256个
bin,此外还可以有16、32、48、128等,256除以bin的大小应该是整数倍。
calcHist函数
calcHist
计算一维数组的直方图(输入图像可以有多通道)
共10个参数
第1个参数 图像数组
第2个参数 输入图像数量
第3个参数 通道数组
第4个参数 可选mask
第5个参数 输出直方图数据(值与对应频次)的n维数组
第6个参数 直方图维数
当通道为1个时,我们选择维度为1维,此时直方图数据就为一维数组
当维度为2个时,我们选择维度为2维,此时直方图数据就为二维数组
………………
也就是说,n张图像 每张图像m个通道 也可以计算出相应的直方图数据
但对于绘制来说,一般都只绘制到2维,3维及以上就很复杂了
第7个参数 histSize( bins数组,x轴长度)
第8个参数 ranges(取值范围数组)
//以下参数暂时用不到
第9个参数 指示直方图bin间隔是否一致
默认为true,即等间隔取值
如果为false,则range不能写{0,255}这种,就要写{1,1,……,1}这种
第10个参数 累计标志(默认为false)
当多张图像的时候,
如果为true,则绘制直每张方图的时候,不会从头清空
会在前者直方图的基础上继续
cvRound
cvRound
将浮点数四舍五入到最近的整数
共1个参数
第1个参数 要处理的浮点数
代码示例
void QuickDemo::showHistogram_demo(Mat& image) {
//三通道分离
std::vector<Mat> bgr;
split(image, bgr);
//定义参数变量
const int channels[1] = { 0 };
Mat b_hist, g_hist, r_hist;
const int bins[1] = { 256 };
float xrange[2] = { 0,255 };
const float* ranges[1] = { xrange };
//计算Blue,Green,Red三通道各自的直方图
calcHist(&bgr[0], 1, channels, Mat(), b_hist, 1, bins, ranges);
calcHist(&bgr[1], 1, channels, Mat(), g_hist, 1, bins, ranges);
calcHist(&bgr[2], 1, channels, Mat(), r_hist, 1, bins, ranges);
//imshow("00", b_hist);
//std::cout << b_hist;
//显示直方图
int hist_w = 512;//画布宽高
int hist_h = 400;
int bin_w = cvRound((double)hist_w / bins[0]);
Mat histImage = Mat::zeros(Size(hist_w, hist_h), CV_8UC3);
//归一化直方图数据为指定范围
normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
绘制直方图曲线
for (int i = 1; i < bins[0]; i++)
{
line(histImage, Point(bin_w * (i - 1), hist_h - cvRound(b_hist.at<float>(i - 1))),//蓝线
Point(bin_w * (i), hist_h - cvRound(b_hist.at<float>(i))), Scalar(255, 0, 0),2, LINE_8,0);
//因为openCV画图为左上角开始故横坐标bin_w * (i - 1)即512/256 再 * i,从左上角画图故纵坐标为直方图纵高 - 根据直方图纵高归一化后的频次
//b_hist.at<float>(i - 1))即在(0,0)这个点的像素值,像素值越高,总的结果越小故图像在上方
line(histImage, Point(bin_w * (i - 1), hist_h - cvRound(g_hist.at<float>(i - 1))),//绿线
Point(bin_w * (i), hist_h - cvRound(g_hist.at<float>(i))), Scalar(0, 255, 0), 2, LINE_8,0);
line(histImage, Point(bin_w * (i - 1), hist_h - cvRound(r_hist.at<float>(i - 1))),//红线
Point(bin_w * (i), hist_h - cvRound(r_hist.at<float>(i))), Scalar(0, 0, 255), 2, LINE_8,0);
}
namedWindow("直方图", WINDOW_FREERATIO);
imshow("直方图", histImage);
}
近的整数
共1个参数
第1个参数 要处理的浮点数
**代码示例**
```c++
void QuickDemo::showHistogram_demo(Mat& image) {
//三通道分离
std::vector<Mat> bgr;
split(image, bgr);
//定义参数变量
const int channels[1] = { 0 };
Mat b_hist, g_hist, r_hist;
const int bins[1] = { 256 };
float xrange[2] = { 0,255 };
const float* ranges[1] = { xrange };
//计算Blue,Green,Red三通道各自的直方图
calcHist(&bgr[0], 1, channels, Mat(), b_hist, 1, bins, ranges);
calcHist(&bgr[1], 1, channels, Mat(), g_hist, 1, bins, ranges);
calcHist(&bgr[2], 1, channels, Mat(), r_hist, 1, bins, ranges);
//imshow("00", b_hist);
//std::cout << b_hist;
//显示直方图
int hist_w = 512;//画布宽高
int hist_h = 400;
int bin_w = cvRound((double)hist_w / bins[0]);
Mat histImage = Mat::zeros(Size(hist_w, hist_h), CV_8UC3);
//归一化直方图数据为指定范围
normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
绘制直方图曲线
for (int i = 1; i < bins[0]; i++)
{
line(histImage, Point(bin_w * (i - 1), hist_h - cvRound(b_hist.at<float>(i - 1))),//蓝线
Point(bin_w * (i), hist_h - cvRound(b_hist.at<float>(i))), Scalar(255, 0, 0),2, LINE_8,0);
//因为openCV画图为左上角开始故横坐标bin_w * (i - 1)即512/256 再 * i,从左上角画图故纵坐标为直方图纵高 - 根据直方图纵高归一化后的频次
//b_hist.at<float>(i - 1))即在(0,0)这个点的像素值,像素值越高,总的结果越小故图像在上方
line(histImage, Point(bin_w * (i - 1), hist_h - cvRound(g_hist.at<float>(i - 1))),//绿线
Point(bin_w * (i), hist_h - cvRound(g_hist.at<float>(i))), Scalar(0, 255, 0), 2, LINE_8,0);
line(histImage, Point(bin_w * (i - 1), hist_h - cvRound(r_hist.at<float>(i - 1))),//红线
Point(bin_w * (i), hist_h - cvRound(r_hist.at<float>(i))), Scalar(0, 0, 255), 2, LINE_8,0);
}
namedWindow("直方图", WINDOW_FREERATIO);
imshow("直方图", histImage);
}