#include
#include
using namespace cv;
using namespace std;
int main(){
Mat src = imread("G:/OpenCV/opencv笔记所用图片/bandian.jpg",1); //默认加载彩色图像,0表示GRAY灰度图,1为BGR彩色图,-1表示加载原图(可能是其他类型如HSV等其他空间)
if (src.empty()){
cout << "could not load image..." << endl;
getchar();
return -1;
}
//当我们读取图片太大时,看不到全局,使用窗口函数可以设置大小
namedWindow("input", WINDOW_AUTOSIZE); //WINDOW_FREERATIO参数可以调整窗口大小。默认图像为WINDOW_AUTOSIZE显示原图,不能调整大小。
imshow("input", src); //若无namedWindow,只有imshow,显示的图像窗口与图片一样大,无法调整窗口大小
//imshow只能显示8位和浮点型的
waitKey(0); //opencv自带阻塞函数,0表示一直阻塞,1表示延迟1毫秒后执行下一步
destroyAllWindows(); //结束程序前将所有窗口销毁
return 0;
}
quickDemo.h
#pragma once
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
class quickDemo
{
public:
void colorSpace_Demo(Mat &image); //002-色彩空间变换(cvtColor)
void mat_creation_demo(Mat &image); //003-图像对象的创建与赋值(m2=m1,clone,copyto)
void pixel_visit_demo(Mat &image); //004-图像像素的读写操作(image.at(row, col),image.at(row, col)[0])
void operators_demo(Mat &image); //005-图像像素的算术操作(+,-,multiply,divide)
void trackbar_demo(Mat &image); //006-trackbar滚动条操作演示-调整图像亮度(add,subtract)
void trackbar_demo2(Mat &image); //007-trackbar滚动条操作演示-参数传递与调整亮度与对比度(addWeighted)
void key_demo(Mat &image); //008-键盘响应操作(waitKey)
void color_style_demo(Mat &image); //009-opencv自带颜色表操作(applyColorMap)
void bitwise_demo(Mat &image); //010-图像像素的逻辑操作(位操作,与、或、非)
void channels_demo(Mat &image); //011-通道分离与合并(split,merge)
void inrange_demo(Mat &image); //012-图像色彩空间转换-变换背景(cvtColor,inRange)
void pixel_static_demo(Mat &image); //013-图像像素值统计(minMaxLoc,meanStdDev)
void drawing_demo(Mat &image); //014-图像几何形状绘制(rectangle,Rect,circle,line,ellipse,RotatedRect)
void random_drawing_demo(); //015-随机数与随机颜色
void polyline_drawing_demo(); //016-多边形填充与绘制(fillPoly,polylines,drawContours)
void mouse_drawing_demo(Mat &image); //017-鼠标操作与响应(setMouseCallback)
void norm_demo(Mat &image); //018-图像像素类型转换与归一化(convertTo,normalize)
void resize_demo(Mat &image); //019-图像缩放与插值(resize)
void flip_demo(Mat &image); //020-图像翻转(flip)
void rotate_demo(Mat &image); //021-图像旋转(getRotationMatrix2D,warpAffine)
void video_demo(Mat &image); //022-视频文件摄像头使用(VideoCapture capture)
void video_demo2(Mat &image); //023-视频处理与保存-帧宽高(capture.get)
void showHistogram(Mat &image); //024-图像直方图-绘制直方图(calcHist,normalize,line)
void histogram_2d_demo(Mat &image); //025-二维直方图(calcHist,minMaxLoc)
void histogram_eq_demo(Mat &image); //026 - 直方图均衡化(equalizeHist)
void blur_demo(Mat &image); //027-图像卷积操作-均值模糊(blur)
void gaussian_blur_demo(Mat &image); //028-高斯模糊(GaussianBlur)
void bifilter_demo(Mat &image); //029-高斯双边模糊(bilateralFilter)
void face_detection_demo(); //030-案例:实时人脸检测(采用dnn模块)
};
quickDemo.cpp
#include "quickDemo.h"
#include <opencv2/dnn.hpp> //要使用dnn模块需要引入此头文件
//002-色彩空间变换
void quickDemo::colorSpace_Demo(Mat &image){
if (image.channels()==3)
{
Mat gray, hsv;
cvtColor(image, hsv, COLOR_BGR2HSV); //可在hsv空间调节亮度通道,再变回BGR空间
cvtColor(image, gray, COLOR_BGR2GRAY);
imshow("HSV", hsv);
imshow("gray image", gray);
imwrite("G:/OpenCV/opencv笔记所用图片/gray.jpg", gray);
imwrite("G:/OpenCV/opencv笔记所用图片/hsv.jpg", hsv);
}else if (image.channels()==1)
{
return;
}
}
//003-图像对象的创建与赋值
void quickDemo::mat_creation_demo(Mat &image){
Mat m1, m2;
m1 = image.clone();
image.copyTo(m2);
cout << "m1.size :"<<m1.size() << endl;
//创建空白图像
Mat m3 = Mat::zeros(Size(8, 8), CV_8UC3); //创建一个8行8列的,CV_8UC1(单通道无符号整型空白图像).CV_8UC3(三通道的)
cout << m3 << endl; //将数组打印出来
m3 = Scalar(127, 127, 127); //可以使用此方式给每个通道赋值
//cout << m3 << endl; //将数组打印出来
cout << "width:"<<m3.cols<<",height:"<<m3.rows<<",channels:"<<m3.channels() << endl;
imshow("创建的图像",m3); //因为值都为127,故应是一张全部灰色的图像
m3 = Scalar(255, 0, 0); //可以使用此方式给每个通道赋值
imshow("创建的图像2", m3); //因为值(255,0,0)(B,G,R),故应是蓝色图像
//Mat m3 = Mat::zeros(Size(8, 8), CV_8UC1); //此时创建的图像的值初始化为1(当为单通道时全部是1,但3通道不同)
//cout << m3 << endl;
//Mat m3 = Mat::zeros(Size(8, 8), CV_8UC3); //只有在3通道的第1个通道为1,其余为0
//cout << m3 << endl;
//当我们使用mat给另外的对象赋值时,相当于将指针重新赋给了另一个对象如下
// Mat image2 = image1;
//当我们修改 image2的值时,也会影响到image1的值,image2就等同于image1.
// 而使用clone或者copyto时,才将image1的数据完全的复制出来给image2,而不是给指针。
// Mat image2 = image1.clone();
//当我们修改 image2的值时,不会影响到image1的值,只会修改 image2的值。
//创建空白图像
Mat m4 = Mat::zeros(Size(400, 400), CV_8UC3);
m4 = Scalar(255, 0, 0);
imshow("图像m4", m4);
Mat m5;
m5 = m4;
m5 = Scalar(0, 0, 255); //此处修改m5的值也会修改m4的值,使用clone或者copyto就不会改变
imshow("图像m4(m5 = m4)", m4);
imshow("图像m5", m5);
//下面这种方式一般在定义卷积核中使用较广
Mat kernel = (Mat_<char>(3, 3) << 0, -1, 0,
-1, 5, -1,
0, -1, 0);
}
//004-图像像素的读写操作
void quickDemo::pixel_visit_demo(Mat &image){
int w = image.cols; //获取列数
int h = image.rows; //获取行数
int dims = image.channels(); //获取通道数
当通道数不同时,遍历像素的方式也不同(使用数组的方式读取像素)
//for (int row = 0; row < h; row++){
// for (int col = 0; col < w; col++){
// if (dims == 1){ //单通道的遍历方式
// int pv = image.at(row, col); //将图像row行col列的值赋值给pv
// //以下操作具有将图像反差化的功能
// image.at(row, col) = 255 - pv; //将255-pv的值重新赋给image图像,image图像中的值就被改变了
// }
// else if (dims == 3){ //三通道的遍历方式
// Vec3b bgr = image.at(row, col); //此时一次性的将三通道的值都获取了
// //以下操作具有将图像反差化的功能
// image.at(row, col)[0] = 255 - bgr[0];
// image.at(row, col)[1] = 255 - bgr[1];
// image.at(row, col)[2] = 255 - bgr[2];
// }
// }
//}
//当通道数不同时,遍历像素的方式也不同(使用指针的方式读取像素,以下功能相当于上面的功能)
for (int row = 0; row < h; row++){
uchar* current_row = image.ptr<uchar>(row); //得到当前行的指针
for (int col = 0; col < w; col++){
if (dims == 1){ //单通道的遍历方式
int pv = *current_row; //将图像row行[0]列的值赋值给pv
*current_row++ = 255 - pv; //将255-pv的值重新赋给图像row行[0]列,从此开始,指针自加,即列自加,变为[row][1],[row][2]...
}
else if (dims == 3){ //三通道的遍历方式
*current_row++ = 255 - *current_row; //此处应为当前列的的第【0】个通道
*current_row++ = 255 - *current_row; //此处的*current_row不是上方的*current_row,应是当前列的的第【1】个通道
*current_row++ = 255 - *current_row; //此处的*current_row不是上方的*current_row,应是当前列的的第【2】个通道
}
}
}
imshow("像素读写演示", image);
}
//005-图像像素的算术操作
void quickDemo::operators_demo(Mat &image){
Mat dst;
dst = image + Scalar(50, 50, 50); //每个通道加50,图像会变亮
imshow("加法操作", dst);
dst = image - Scalar(50, 50, 50); //每个通道-50,图像会变暗
imshow("减法操作", dst);
Mat m = Mat::zeros(image.size(), image.type());//创建一个和image大小相同,类型相同,值为0的图片
m = Scalar(2, 2, 2);
dst = image /m; //两张大小、类型相同的图片除法操作
imshow("除法操作", dst);
//使用opencv自带的api函数进行加减乘除操作
//两张大小、类型相同的图片乘法操作不能使用(dst = image *m;),会报错
multiply(image, m, dst); //使用multiply可以实现两图像的乘法运算
imshow("multiply操作", dst);
add(image, m, dst); //使用add可以实现两图像的加法运算
imshow("add操作", dst);
subtract(image, m, dst); //使用subtract可以实现两图像的减法运算
imshow("subtract操作", dst);
divide(image, m, dst); //使用divide可以实现两图像的除法运算
imshow("divide操作", dst);
//通过对图像像素点的操作实现算术运算
//当通道数不同时,遍历像素的方式也不同(使用数组的方式读取像素)
Mat dst2 = Mat::zeros(image.size(),image.type()); //创建一个背景图像
int w = image.cols; //获取列数
int h = image.rows; //获取行数
int dims = image.channels(); //获取通道数
for (int row = 0; row < h; row++){
for (int col = 0; col < w; col++){
if (dims == 1){ //单通道的遍历方式
int p1 = image.at<uchar>(row, col); //将图像row行col列的值赋值给p1
int p2 = m.at<uchar>(row, col);
//以下操作具有将图像反差化的功能
dst2.at<uchar>(row, col) = saturate_cast<uchar>( p1 + p2); //将p1和p2的像素点相加赋值给dst2图像(当前行列)
//saturate_cast(p1 + p2)可以将p1和p2相加得到的值限制在0~255之间,防止值超过此区间
}
else if (dims == 3){ //三通道的遍历方式
Vec3b p1 = image.at<Vec3b>(row, col); //image的像素点赋值给p1(当前行列)
Vec3b p2 = m.at<Vec3b>(row, col); //m的像素点赋值给p2(当前行列)
//将p1和p2的像素点相加赋值给dst2图像(当前行列)
dst2.at<Vec3b>(row, col)[0] = saturate_cast<uchar>(p1[0] + p2[0]);
dst2.at<Vec3b>(row, col)[1] = saturate_cast<uchar>(p1[1] + p2[1]);
dst2.at<Vec3b>(row, col)[2] = saturate_cast<uchar>(p1[2] + p2[2]);
}
}
}
imshow("自定义像素点相加", dst2);
}
//006-create-trackbar("Value Bar")的回调函数(此处使用add等算术操作函数调整亮度)
Mat src; //此处使用全局变量来传递数据src到回调函数中(也可以使用void*来在参数中传递数据)
static void on_track(int lightness, void *userdata){
Mat m = Mat::zeros(src.size(), src.type()); //创建背景图像
m = Scalar(lightness, lightness, lightness); //给此图像赋值
Mat dst;
add(src, m, dst); //两图像相加并输出dst
//subtract(src, m, dst); //两图像相减并输出dst
imshow("亮度调整", dst); //显示在窗口上
}
//006-trackbar滚动条操作演示-调整图像亮度
void quickDemo::trackbar_demo(Mat &image){
namedWindow("亮度调整", WINDOW_AUTOSIZE);
int maxValue = 255;
int lightness = 50;
src = image;
createTrackbar("Value Bar", "亮度调整", &lightness, maxValue,on_track); //创建亮度调整的滑动条
//参数1:滑动条名称,参数2:窗口名称,参数3:滑动条初始值,参数4:滑动条最大范围,参数5:回调函数,参数6:传递数据(万能指针)
on_track(50, &image); //调用一次回调函数,令初始时就有显示(可以不调用),
//此处应注意,第二个参数在上面传递数据时不能为0,若初始化时指针为0,就会报错(空指针在传递数据时不要存在)
}
//007-create-trackbar("Value Bar")的回调函数(此处使用addWeighted调整亮度)
static void on_track2_1(int lightness, void *image){
Mat src = *(Mat*)image; //(Mat*)image将指针image强转Mat类型的,再使用*将里面的值取出,又是赋值操作,此时src就是image
//此处使用的是createbar自带的传递数据功能,也可以使用全局变量来传递数据
Mat m = Mat::zeros(src.size(), src.type()); //创建背景图像
m = Scalar(lightness, lightness, lightness); //给此图像赋值
Mat dst;
addWeighted(src, 1.0, m, 0, lightness, dst); //两张图以一定的权重相加(src*1+m*0+lightness),此处m图像无用,只对src图像进行加法操作
imshow("亮度与对比度调整", dst); //显示在窗口上
}
//007-create-trackbar("Value Bar")的回调函数(此处使用addWeighted调整对比度)
static void on_track2_2(int contrast_value, void *image){
Mat src = *(Mat*)image; //(Mat*)image将指针image强转Mat类型的,再使用*将里面的值取出,又是赋值操作,此时src就是image
//此处使用的是createbar自带的传递数据功能,也可以使用全局变量来传递数据
Mat m = Mat::zeros(src.size(), src.type()); //创建背景图像
Mat dst;
addWeighted(src, contrast_value, m, 0, 0, dst); //两张图以一定的权重相加(src*contrast_value+m*0+0),此处m图像无用,只对src图像进行乘法运算,增加其对比度
imshow("亮度与对比度调整", dst); //显示在窗口上
}
//007-trackbar滚动条操作演示-参数传递与调整亮度与对比度
void quickDemo::trackbar_demo2(Mat &image){
namedWindow("亮度与对比度调整", WINDOW_AUTOSIZE);
int maxValue = 255;
int lightness = 50;
createTrackbar("Value Bar", "亮度与对比度调整", &lightness, maxValue, on_track2_1, &image); //创建亮度调整的滑动条
//参数1:滑动条名称,参数2:窗口名称,参数3:滑动条初始值,参数4:滑动条最大范围,参数5:回调函数,参数6:传递数据(万能指针)
int contrast_value = 1.2;
createTrackbar("Contrast Bar", "亮度与对比度调整", &contrast_value, 5, on_track2_2, &image);//创建对比度调整的滑动条
on_track2_1(50, &image); //调用一次回调函数,令初始时就有显示(可以不调用),此处应注意,第二个参数在上面传递数据时不能为0,若初始化时指针为0,就会报错(空指针在传递数据时不要存在)
//on_track2_2(50, &image);
}
//008-键盘响应操作
void quickDemo::key_demo(Mat &image){
Mat dst=Mat::zeros(image.size(),image.type());
while (true){ //在此循环不断监听键盘操作
char c = waitKey(100); //停顿100毫秒,waitKey(100)返回值为100毫秒内键盘按键的ASCII码值。
//cout << c << endl; //输出waitekey接收到的键值
//在此无限循环中加入退出机制(按esc退出循环)
if ((int)c == 27){ //此处将c类型强转,或者int c = waitKey(100)
break; //27为退出键esc的数值
}
//判断输入键值,进行一定操作
if ((int)c == 49){ //判断当按下为1时,或者不强转直接c=='1'
cout << "you enter key #"<<c << endl;
cvtColor(image, dst, COLOR_BGR2GRAY); //将其变为灰度图像
}
if ((int)c == 50){ //判断当按下为2时
cout << "you enter key #" << c << endl;
cvtColor(image, dst, COLOR_BGR2HSV); //将其变为HSV图像
}
if (c == '3'){ //此处也可以使用此种方式判断,或者(int)c == 51
cout << "you enter key #" << c << endl;
dst = Scalar(50, 50, 50);
add(image, dst, dst);
}
imshow("键盘响应", dst);
}
}
//009-opencv自带颜色表操作
void quickDemo::color_style_demo(Mat &image){
//COLORMAP_AUTUMN = 0, //!< ![autumn](pics/colormaps/colorscale_autumn.jpg)
// COLORMAP_BONE = 1, //!< ![bone](pics/colormaps/colorscale_bone.jpg)
// COLORMAP_JET = 2, //!< ![jet](pics/colormaps/colorscale_jet.jpg)
// COLORMAP_WINTER = 3, //!< ![winter](pics/colormaps/colorscale_winter.jpg)
// COLORMAP_RAINBOW = 4, //!< ![rainbow](pics/colormaps/colorscale_rainbow.jpg)
// COLORMAP_OCEAN = 5, //!< ![ocean](pics/colormaps/colorscale_ocean.jpg)
// COLORMAP_SUMMER = 6, //!< ![summer](pics/colormaps/colorscale_summer.jpg)
// COLORMAP_SPRING = 7, //!< ![spring](pics/colormaps/colorscale_spring.jpg)
// COLORMAP_COOL = 8, //!< ![cool](pics/colormaps/colorscale_cool.jpg)
// COLORMAP_HSV = 9, //!< ![HSV](pics/colormaps/colorscale_hsv.jpg)
// COLORMAP_PINK = 10, //!< ![pink](pics/colormaps/colorscale_pink.jpg)
// COLORMAP_HOT = 11, //!< ![hot](pics/colormaps/colorscale_hot.jpg)
// COLORMAP_PARULA = 12
//opencv3.0的版本中的色彩表只有12中类型,在opencv4.1中则高达19种
//通过定义一个数组(里面的值实际为整数数字),来在后面实现图片的轮流播放
int colorMap[] = {
COLORMAP_AUTUMN, //!< ![autumn](pics/colormaps/colorscale_autumn.jpg)
COLORMAP_BONE, //!< ![bone](pics/colormaps/colorscale_bone.jpg)
COLORMAP_JET, //!< ![jet](pics/colormaps/colorscale_jet.jpg)
COLORMAP_WINTER, //!< ![winter](pics/colormaps/colorscale_winter.jpg)
COLORMAP_RAINBOW, //!< ![rainbow](pics/colormaps/colorscale_rainbow.jpg)
COLORMAP_OCEAN, //!< ![ocean](pics/colormaps/colorscale_ocean.jpg)
COLORMAP_SUMMER, //!< ![summer](pics/colormaps/colorscale_summer.jpg)
COLORMAP_SPRING, //!< ![spring](pics/colormaps/colorscale_spring.jpg)
COLORMAP_COOL, //!< ![cool](pics/colormaps/colorscale_cool.jpg)
COLORMAP_HSV, //!< ![HSV](pics/colormaps/colorscale_hsv.jpg)
COLORMAP_PINK, //!< ![pink](pics/colormaps/colorscale_pink.jpg)
COLORMAP_HOT, //!< ![hot](pics/colormaps/colorscale_hot.jpg)
COLORMAP_PARULA
};
Mat dst;
int index = 0;
while (true){
int c = waitKey(1000);
if (c == 27){
break;
}
applyColorMap(image, dst, colorMap[index % 12]); //index%12保证此数值一直在12之内
cout << "第" << index % 12 << "种颜色风格" << endl;
index++;
imshow("颜色风格", dst);
//如果输入键值为s保存当前图像
string imagename = "颜色风格-" + to_string(index % 12)+".jpg";
char s = waitKey(1000);
//String类下valueOf可将任何类型转换成String类型
if (s == 's'){
imwrite(imagename, dst);
}
}
}
//010-图像像素的逻辑操作(位操作,与、或、非)
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(m1, Rect(100,100,80,80),Scalar(255,255,0),-1); //绘制矩形并填充
rectangle(m2, Rect(150, 150, 80, 80), Scalar(0, 255, 255), -1); //绘制矩形并填充
imshow("m1", m1);
imshow("m2", m2);
Mat dst;
bitwise_and(m1, m2, dst); //m1与m2的交集Scalar(255,255,0) && Scalar(0, 255, 255)==Scalar(0, 255, 0),只剩下G通道的值不为0
imshow("m1和m2的与操作", dst);
bitwise_or(m1, m2, dst); //m1与m2的交集Scalar(255,255,0) || Scalar(0, 255, 255)==Scalar(255, 255, 255),BGR通道值全为255,故此区域为白色
imshow("m1和m2的或操作", dst);
bitwise_xor(m1, m2, dst); //m1与m2的交集Scalar(255,255,0) XOR Scalar(0, 255, 255)==Scalar(255,0, 255)
imshow("m1和m2的异或操作", dst);
bitwise_not(image, dst); //对image取反操作(使用~image也可以直接进行取反操作)
imshow("对image的非操作", dst);
}
//011-通道分离与合并
void quickDemo::channels_demo(Mat &image){
//通道分离的方式有两种
Mat aChannels[3]; //利用数组分离,此处必须为3,否则会报错(split(image, aChannels); )
vector<Mat> mv; //利用vector对象分离
//src为要分离的Mat对象
split(image, mv); //split分离后的是单通道图像
imshow("蓝色单通道", mv[0]);
imshow("绿色单通道", mv[1]);
imshow("红色单通道", mv[2]);
Mat dst;
merge(mv,dst); //对vector对象的合并(和原图像相同三通道)
imshow("使用vector合并成原图", dst);
//可以对单通道进行某些操作后再进行合并
Mat mzero = Mat::zeros(mv[0].size(), mv[0].type()); //创建一个和mv[0]单通道大小类型相同的,值为0的单通道图像,通道合并时使用
aChannels[0] = mv[0].clone(); //使用clone不会影响到mv[0]
aChannels[1] = mzero.clone();
aChannels[2] = mzero.clone();
merge(aChannels, 3, dst); //对数组的合并(三通道)
imshow("蓝色三通道", dst);
aChannels[0] = mzero.clone();
aChannels[1] = mv[1].clone();
aChannels[2] = mzero.clone();
merge(aChannels, 3, dst); //对数组的合并(三通道),参数2:合并数组的个数
imshow("绿色三通道", dst);
aChannels[0] = mzero.clone();
aChannels[1] = mzero.clone();
aChannels[2] = mv[2].clone();
merge(aChannels, 3, dst); //对数组的合并(三通道)
imshow("红色三通道", dst);
//进行通道的混合,可以对多个图像混合
int from_to[] = { 0, 2, //表示将第1个图像的[0]通道数据,传递给第2个图像的[2]通道
1, 1, //表示将第1个图像的[1]通道数据,传递给第2个图像的[1]通道
2, 0,}; //表示将第1个图像的[2]通道数据,传递给第2个图像的[0]通道
mixChannels(&image, 1, &dst, 1, from_to, 3); //此处的dst在上面的变换已经是3通道且大小类型与image相同
//参数1:输入图像地址,
//参数2:输入图像的个数1,
//参数3:输出图像地址,输出图像个数1,
//参数4:设置输入矩阵的通道对应输出矩阵的通道,
//参数5:共有几组输入输出通道的关系,此处是3组
imshow("mixChannels混合通道", dst);
Mat dst2 = Mat::zeros(image.size(), image.type()); //此处dst2的大小类型需和image相同,否则执行mixChannels会报错
int from_to2[] = {0, 0};
mixChannels(&image, 1, &dst2, 1, from_to2, 1); //此处也能显示蓝色,和上面的蓝色单通道颜色相同
imshow("蓝色混合通道", dst2);
}
//012-图像色彩空间转换-背景变换(cvtColor,inRange)
void quickDemo::inrange_demo(Mat &image){
Mat hsv;
cvtColor(image, hsv, COLOR_BGR2HSV); //hsv色彩空间表https://blog.csdn.net/leo_888/article/details/88284251
//通过hsv空间表,确定出绿色的范围(H:35-77,S:43-255,V:46-255),使用inrange将此范围提取出来
Mat mask;
inRange(hsv, Scalar(35, 43, 46), Scalar(77, 255, 255), mask); //此处提取的是绿色区域部分
imshow("inRange_mask", mask);
Mat redback = Mat::zeros(image.size(), image.type());
redback = Scalar(40, 40, 200); //将此创建的背景图片变为红色
bitwise_not(mask, mask); //此处对mask取反得到人物的区域
imshow("bitwise_not_mask", mask);
image.copyTo(redback, mask); //此处将image图像在mask中不为0的点拷贝到redback上,mask为0的区域就不要了
imshow("redback", redback); //显示背景为红色,前景为人的图像
}
//013-图像像素值统计(minMaxLoc,meanStdDev)
void quickDemo::pixel_static_demo(Mat &image){
double minv, maxv; //最小值,最大值
Point minLoc, maxLoc; //最小值位置,最大值位置
vector<Mat> mv;
split(image, mv);
for (int i = 0; i < mv.size(); i++){
minMaxLoc(mv[i], &minv, &maxv, &minLoc, &maxLoc, Mat()); //输入矩阵应为单通道
cout << "No.channels:" << i << ",min value:" << minv << ",max value:" << maxv << endl;
}
Mat mean, stddev;
meanStdDev(image, mean, stddev); //参数1:输入矩阵(1-4通道),参数2:计算均值,参数3:计算方差,参数4:mask(指定ROI区域)
cout << "mean:" << mean <<endl<< "stddeve:" << stddev << endl;
cout << "mean-type:" << mean.type() << endl << "stddeve-type:" << stddev.type() << endl;
//mean和stddev的type都是6,故矩阵类型为CV_64F,数据是double类型的,所以取值时应at
//CV_8U=ucahr,CV_8S=cahr,CV_32S=int,CV_32F=float,CV_64F=double
//提取单通道的均值及均方差(mean是个单通道的矩阵(使用imagewatch查看其状态),提取其值使用)
double meanB = mean.at<double>(0, 0); //一定要注意at<>中的取值类型,不对就会报错,此处用imagewatch调试,看不能使用int,只能使用double类型
cout << "meanB:" << meanB << endl;
double meanG = mean.at<double>(1, 0); //一定要注意at<>中的取值类型,不对就会报错,此处用imagewatch调试,看不能使用int,只能使用double类型
cout << "meanG:" << meanG << endl;
double meanR = mean.at<double>(2, 0); //一定要注意at<>中的取值类型,不对就会报错,此处用imagewatch调试,看不能使用int,只能使用double类型
cout << "meanR:" << meanR << endl;
}
//014-图像几何形状绘制(rectangle,Rect,circle,line,ellipse,RotatedRect)
void quickDemo::drawing_demo(Mat &image){
Mat bg = Mat::zeros(image.size(), image.type());
//绘制矩形
Rect rect;
rect.x = 100;
rect.y = 50;
rect.width = 200;
rect.height = 200;
rectangle(bg, rect, Scalar(0, 0, 255), -1,8); //参数4为-1时表示填充
//绘制圆
circle(bg, Point(300, 250), 15, Scalar(255, 0, 0), 2,8);
//绘制线
line(bg, Point(100, 50), Point(300, 250), Scalar(0, 255, 0), 2,LINE_AA); //LINE_AA反锯齿(更平滑)
//绘制椭圆
RotatedRect rrt;
rrt.center = Point(200,100);
rrt.size = Size(100, 200);
rrt.angle = 45.0; //椭圆角度
ellipse(bg, rrt, Scalar(0, 255, 255), 2, 8);
imshow("绘制演示", bg);
Mat dst;
addWeighted(image, 0.7, bg, 0.3, 0, dst);
imshow("addWeighted演示", dst);
}
//015-随机数与随机颜色
void quickDemo::random_drawing_demo(){
Mat canavas = Mat::zeros(Size(512, 512), CV_8UC3);
int w = canavas.cols;
int h = canavas.rows;
RNG rng(1234);
while (true)
{
int c = waitKey(100);
if (c==27){ //当按下ESC键时退出循环
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);
canavas = Scalar(0, 0, 0); //每次循环将图像变为黑色(刷新图像,可以实现每次绘制一条线,若注释则绘制多条线)
line(canavas, Point(x1, y1), Point(x2, y2), Scalar(b, g, r), 1, LINE_AA);
imshow("随机绘制演示", canavas);
}
}
//016-多边形填充与绘制(fillPoly,polylines,drawContours)
void quickDemo::polyline_drawing_demo(){
Mat canavas = 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);
vector<Point> pts;
pts.push_back(p1);
pts.push_back(p2);
pts.push_back(p3);
pts.push_back(p4);
pts.push_back(p5);
//fillPoly(canavas, pts, Scalar(255, 255, 0), 8); //此函数专用填充多边形(此种形式opencv3.0一直报错(可以使用其重载),opencv4可以实现)
//imshow("fillPoly多边形填充", canavas);
polylines(canavas, pts, true, Scalar(0, 0, 255), 2, 8,0); //此函数无法将参数5变为-1来填充,会报错
//参数1:输入输出图像矩阵,参数2:输入点集,参数3:是否封闭,参数4:颜色,参数5:线宽,参数6:线型,参数7:偏置
imshow("polylines多边形绘制", canavas);
//通过点集绘制轮廓
vector<vector<Point>> contours;
contours.push_back(pts);
drawContours(canavas, contours, -1, Scalar(255, 0, 0), -1); //参数5位-1时填充
imshow("drawContours多边形绘制", canavas);
}
//017-定义鼠标事件的回调函数
Point sp(-1, -1);
Point ep(-1, -1);
Mat temp; //用于保存原图,已完成对之前画有矩形的图片的擦除(所以拖动时永远是最新的矩形了)
static void on_draw(int event, int x, int y, int flags, void *userdata){
Mat image = *((Mat*)userdata);
if (event==EVENT_LBUTTONDOWN) //当鼠标按下时,得到初始位置
{
sp.x = x;
sp.y = y;
cout << "start point:" << sp << endl;
}
else if (event == EVENT_LBUTTONUP) //当鼠标抬起时,得到结束位置
{
ep.x = x;
ep.y = y;
//cout << "end point:" << sp << endl;
//绘制矩形窗
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);
Mat imageBox = image(box);
imshow("ROI区域", imageBox); //此处直接截取ROI区域
rectangle(image, box, Scalar(0, 0, 255), 1, 8, 0);
imshow("鼠标绘制", image); //绘制完成后要更新
//结束后,为下一次做准备
sp.x = -1;
sp.y = -1;
}
}
else if (event == EVENT_MOUSEMOVE){ //当鼠标移动过程中实时显示矩形大小
if (sp.x>0 && sp.y>0) //只有当鼠标按下了,才能有所响应
{
ep.x = x;
ep.y = y;
if (ep.x>image.cols || ep.y>image.rows)
{
cout << "无法截取边界,请重新截取" << endl;
getchar();
exit(1); //当绘制的矩形超出图像坐标时,要退出,否则会报错
}
//cout << "end point:" << sp << endl;
//绘制矩形窗
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), 1, 8, 0);
imshow("鼠标绘制", image); //绘制完成后要更新
}
}
}
}
//017-鼠标操作与响应(setMouseCallback)
void quickDemo::mouse_drawing_demo(Mat &image){
namedWindow("鼠标绘制", WINDOW_AUTOSIZE);
setMouseCallback("鼠标绘制", on_draw,(void*)(&image));
imshow("鼠标绘制", image);
temp = image.clone();
}
//018-图像像素类型转换与归一化(convertTo,normalize)
void quickDemo::norm_demo(Mat &image){
//一般在深度学习等预处理图像时将数据归一化
//将image变为float数据类型
Mat dst;
cout <<"image.type():"<< image.type() << endl;
image.convertTo(image, CV_32F);
imshow("浮点型图像", image); //浮点型的图像要想正常显示必须是0-1之间的,否则在0-255之间的浮点数无法正常显示
cout << "image.type():" << image.type() << endl;
//CV_8UC3=16,CV_32FC3=21
//CV_8U=ucahr,CV_8S=cahr,CV_32S=int,CV_32F=float,CV_64F=double
//将0-255的数据值范围归一化为0-1,要在0-1数据应为浮点型(https://blog.csdn.net/DP323/article/details/80273996)
normalize(image, dst, 1.0, 0, NORM_MINMAX);//输入数组是浮点型的,输出的和输入的数值类型相同,只是取值范围不同(通过imagewatch查看)
//image若不转类型,之前是无符号整型,当归一化0-1时,数值都是0.几,就会成为黑色图像
//参数1:输入数组
//参数2::输出数组,支持原地运算
//参数3:range normalization模式的最小值
//参数4:range normalization模式的最大值,不用于norm normalization(范数归一化)模式。
//参数5::归一化的类型,可以有以下的取值:
//NORM_MINMAX:数组的数值被平移或缩放到一个指定的范围,线性归一化,一般较常用。
//NORM_INF : 此类型的定义没有查到,根据OpenCV 1的对应项,可能是归一化数组的C - 范数(绝对值的最大值)
//NORM_L1 : 归一化数组的L1 - 范数(绝对值的和)
//NORM_L2 : 归一化数组的(欧几里德)L2 - 范数
cout << "dst.type():" << dst.type() << endl;
imshow("图像数据归一化", dst);
}
//019-图像缩放与插值
void quickDemo::resize_demo(Mat &image){
//在深度学习中可能需要将其转换成一定大小的图片,并归一化到0-1
Mat zoomin, zoomout;
int h = image.rows;
int w = image.cols;
resize(image, zoomin, Size(w / 2, h / 2), 0, 0, INTER_LINEAR);
/*参数1:输入图像
参数2:输出图像,形态和输入图相同,当dsize不等于0,输出图尺寸会和dsize相同, 当dsize等于0,输出图尺寸会由输入图尺寸、fx、fy计算而得
参数3:输出图像大小,若为零,则参数4、5不能和其同时为0,dsize = Size(round(fxsrc.cols), round(fysrc.rows))
参数4:水平缩放比例,当输入为0时,fx = (double)dsize.width / src.cols
参数5:垂直缩放比例, 当输入为0时,fy = (double)dsize.height / src.rows
参数6:插值方式,如下
INTER_NEAREST - 最近邻插值
INTER_LINEAR - 线性插值(默认),此种方法要比最近邻插值更平滑
INTER_AREA - 区域插值
INTER_CUBIC - 三次样条插值
INTER_LANCZOS4 - Lanczos插值*/
imshow("zoomin", zoomin);
resize(image, zoomout, Size(w *1.5, h * 1.5), 0, 0, INTER_LINEAR);
imshow("zoomout", zoomout);
}
//020-图像翻转(flip)
void quickDemo::flip_demo(Mat &image){
Mat dst;
flip(image, dst, 0);
/*参数1:输入图像
参数2:输出图像
参数3:>0: 沿x - 轴翻转, 0 : 沿y - 轴翻转, <0 : x、y轴同时翻转*/
imshow("图像翻转", dst);
}
//021-图像旋转(getRotationMatrix2D,warpAffine)
void quickDemo::rotate_demo(Mat &image){
Mat dst, M;
int w = image.cols;
int h = image.rows;
//获得旋转矩阵M
M = getRotationMatrix2D(Point(w/2,h/2), 45, 1.0); //参数1;确定原图像的旋转中心,参数2:旋转角度,参数3:缩放因子(1.0表示大小不变)
//对原图像进行旋转操作1(输出图像大小不变,但旋转的一部分无法全部显示)
warpAffine(image, dst, M, image.size(), INTER_LINEAR,0,Scalar(0,0,255));
/*.src: 输入图像
.dst : 输出图像,尺寸由dsize指定,图像类型与原图像一致
.M : 2X3的变换矩阵
.dsize : 指定图像输出尺寸
.flags : 插值算法标识符,有默认值INTER_LINEAR
.borderMode : 边界像素模式,有默认值BORDER_CONSTANT
.borderValue : 边界颜色取值,有默认值Scalar()即0*/
imshow("旋转演示1", dst);
//旋转操作2,改变输出旋转图像大小,使其将旋转后的图像全部显示
//M矩阵使用imagewatch查看其内容,2行3列,返回矩阵https://blog.csdn.net/limit_ing/article/details/61952187
//
//确定新的旋转图像大小及偏移
double cos = abs(M.at<double>(0, 0)); //得到角度a的余弦值cos(a) ,abs()求元素的绝对值 ,CV_64F=double
double sin = abs(M.at<double>(0, 1)); //得到角度a的正弦值sin(a)
int nw = cos*w + sin*h; //获取新的图像宽度
int nh = sin*w + cos*h; //获取新的图像宽度
M.at<double>(0, 2) = M.at<double>(0, 2) + (nw / 2 - w/ 2); //更改旋转矩阵M中的(0,2)处的值,此处含义类似更改旋转中心宽度
M.at<double>(1, 2) = M.at<double>(1, 2) + (nh/ 2 - h / 2); //更改旋转矩阵M中的(1,2)处的值,此处含义类似更改旋转中心高度
warpAffine(image, dst, M, Size(nw,nh), INTER_LINEAR, 0, Scalar(255, 0, 255));
imshow("旋转演示2", dst);
}
//022-视频文件摄像头使用(VideoCapture)
void quickDemo::video_demo(Mat &image){
//VideoCapture capture(0); //初始化一个capture对象用来获取视频摄像头,0表示获取电脑本机摄像头
VideoCapture capture("G:\\OpenCV\\opencv所用视频\\wuDao.avi"); //从指定路径中读取视频
if (!capture.isOpened())
{
std::cout << "Read video Failed !" << std::endl;
getchar();
return;
}
Mat frame;
while (true)
{
capture.read(frame); //将视频读取到Mat矩阵中
if (frame.empty())
{
break;
}
imshow("frame",frame);
flip(frame, frame, 1);//电脑摄像头的视频有镜像,将其再反转一下
//TODO:do something...(可以在此处做一些对图像的操作)
cvtColor(frame, frame, COLOR_BGR2GRAY);
imshow("灰度", frame);
//当按键为ESC时退出循环
int c = waitKey(1);
if (c==27)
{
break;
}
}
//资源释放
capture.release();
另外一种读取视频文件方式(https://blog.csdn.net/u010368556/article/details/79186992)
//cv::VideoCapture capture;
//capture.open("G:\\OpenCV\\opencv所用视频\\wuDao.avi");
//if (!capture.isOpened())
//{
// std::cout << "Read video Failed !" << std::endl;
// getchar();
// return;
//}
//cv::Mat frame;
//cv::namedWindow("video test");
//int frame_num = capture.get(cv::CAP_PROP_FRAME_COUNT);
//std::cout << "total frame number is: " << frame_num << std::endl;
//for (int i = 0; i < frame_num - 1; ++i)
//{
// capture >> frame; //第二种方式
// //capture.read(frame);
// imshow("video test", frame);
// if (cv::waitKey(30) == 'q')
// {
// break;
// }
//}
//cv::destroyWindow("video test");
//capture.release();
}
//023-视频处理与保存-帧宽高(capture.get)(未完成)
void quickDemo::video_demo2(Mat &image){
//https://blog.csdn.net/cv_jason/article/details/54619505
VideoCapture capture(0); //初始化一个capture对象用来获取视频摄像头,0表示获取电脑本机摄像头
//VideoCapture capture("G:\\OpenCV\\opencv所用视频\\wuDao.avi"); //从指定路径中读取视频
if (!capture.isOpened())
{
std::cout << "Read video Failed !" << std::endl;
getchar();
return;
}
//set方式可以设置视频宽高,但如果相机不支持此种分辨率输出set也是无法取的
int frame_width = capture.get(CAP_PROP_FRAME_WIDTH); //获取视频帧的宽
int frame_height = capture.get(CAP_PROP_FRAME_HEIGHT); //获取视频帧的高
int frame_count = capture.get(CAP_PROP_FRAME_COUNT); //获取视频总帧数
int fps = capture.get(CAP_PROP_FPS); //获取视频FPS(FPS表示每秒处理多少帧画面,处理速度越快,性能越好,FPS一种衡量标准)
cout << "frame width:" << frame_width << endl;
cout << "frame height:" << frame_height << endl;
cout << "frame count:" << frame_count << endl;
cout << "fps:" << fps << endl;
int codec = capture.get(CAP_PROP_FOURCC); //获取视频文件格式
//cout << "codec:" << codec << endl; //文件格式为10进制的
//char s[20];
//sprintf(s, "%x", codec); //将获取的codec转换成16进制的字符串
//printf("视频文件格式16进制:%s\n", s); //将获取的codec以16进制的形式输出
//直接输出视频编码格式
cout << "视频文件格式:" << char(codec&0xFF) << char((codec>>8)&0xFF) << char((codec>>16)&0xFF) << char((codec>>24)&0xFF) << endl;
//&是位操作符,10进制codec的二进制数与16进制0xFF的二进制数进行与操作,而codec的最低8位被保留了下来,其余高位全部变为0,此时再对截断后的8位char就得到字符
//(11111111 11111111 11111111 10000001) & (0x00000000 00000000 00000000 11111111) = 00000000 00000000 00000000 10000001
//(codec>>8)&0xFF对codec二进制的第2个高8位进行截断
//(codec>>16)&0xFF对codec二进制的第3个高8位进行截断
//(codec>>24)&0xFF对codec二进制的第4个高8位进行截断
//视频保存大小与获得的视频大小相同(此处使用的opencv4)
fps = 30; //当为打开摄像头设备时,此处fps一定要赋值,否则录制的视频无法打开
VideoWriter writer;
writer.open("G:\\OpenCV\\opencv所用视频\\wu4.avi", writer.fourcc('M', 'J', 'P', 'G'), fps, Size(frame_width, frame_height), true); //https://blog.csdn.net/Day_upon/article/details/85991445
//以下编码格式在OpenCV4中都可以运行
//CV_FOURCC('M','J','P','G') 注意此种格式保存的视频一直无法打开(在opencv3下)
//CV_FOURCC('P', 'I', 'M', '1') = MPEG - 1 codec(无法打开)(在opencv3下)
// CV_FOURCC('M', 'J', 'P', 'G') = motion - jpeg codec(无法打开)(在opencv3下)
// CV_FOURCC('M', 'P', '4', '2') = MPEG - 4.2 codec(保存的avi可以打开)(在opencv3下)
// CV_FOURCC('D', 'I', 'V', '3') = MPEG - 4.3 codec(保存的avi可以打开)(在opencv3下)
// CV_FOURCC('D', 'I', 'V', 'X') = MPEG - 4 codec(可以打开)(在opencv3下)
// CV_FOURCC('U', '2', '6', '3') = H263 codec(未测试)
// CV_FOURCC('I', '2', '6', '3') = H263I codec(未测试)
// CV_FOURCC('F', 'L', 'V', '1') = FLV1 codec(无法打开)
Mat frame;
while (true){
capture.read(frame); //将视频读取到Mat矩阵中
if (frame.empty()){
break;
}
imshow("frame", frame);
//flip(frame, frame, 1);//电脑摄像头的视频有镜像,将其再反转一下,当为MJPG编码格式时无需翻转
//TODO:do something...(可以在此处做一些对图像的操作)
/*cvtColor(frame, frame, COLOR_BGR2GRAY);
imshow("灰度", frame);*/
//opencv3版本一直保存失败(原因已经明确)
writer.write(frame); //保存视频(opencv只能处理视频不处理音频,处理大小有限制,不要超过2G)
//writer << frame;
//当按键为ESC时退出循环
int c = waitKey(1000/30);
if (c == 27)
{
break;
}
}
//资源释放
capture.release();
writer.release();
}
//024-图像直方图(calcHist,normalize,line)
void quickDemo::showHistogram(Mat &image){
//cvtColor(image, image, COLOR_BGR2GRAY);
//imshow("gray", image);
if (image.channels() == 3)
{
//三通道分离
vector<Mat> bgr_plane;
split(image, bgr_plane);
//定义参数变量
const int channels[1] = { 0 };
const int bins[1] = { 256 };
float hranges[2] = { 0, 255 };
const float* ranges[1] = { hranges };
Mat b_hist;
Mat g_hist;
Mat r_hist;
//计算Blue,green,red通道的直方图
calcHist(&bgr_plane[0], 1, 0, Mat(), b_hist, 1, bins, ranges);
calcHist(&bgr_plane[1], 1, 0, Mat(), g_hist, 1, bins, ranges);
calcHist(&bgr_plane[2], 1, 0, Mat(), r_hist, 1, bins, ranges);
//参数1;输入图像地址,参数2:1张图片(指针可以指向多个图),参数3:0表示一个通道,参数4:mask使用Mat()空计算整张图像,
//参数5:输出,参数6:1维,参数7:bins(256个灰度级别),参数8;ranges直方图范围
//用mv来接收计算得到直方图数据,在后面的y坐标绘制中会使用到(防止b_hist等中途被修改)
Mat mv[3];
mv[0] = b_hist.clone(); //在后面的y坐标显示会用到此
mv[1] = g_hist.clone();
mv[2] = r_hist.clone();
//opencv的从数据到图像需要自己绘制
//显示直方图
int hist_w = 512;
int hist_h = 400;
int bin_w = cvRound((double)hist_w / bins[0]); //cvRound():返回跟参数最接近的整数值,即四舍五入;
Mat histImage = Mat::zeros(408, 560, 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());
//绘制直方图曲线(在一个mat图像上使用line绘制)
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, 8, 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, 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, 8, 0);
}
//绘制x坐标
for (int i = 0; i < bins[0]; i++){
if ((i) % 15 == 0){
//绘制刻度线
line(histImage, Point(bin_w*(i), 396),
Point(bin_w*(i), 390), Scalar(255, 255, 0), 2, 8, 0);
//绘制数字
string stri = to_string(i);
putText(histImage, stri, Point(bin_w*(i), 405), FONT_HERSHEY_COMPLEX, 0.4, Scalar(255, 255, 255), 1, 8);
//在opencv3下是CV_FONT_HERSHEY_COMPLEX,而在opencv4就是FONT_HERSHEY_COMPLEX
}
}
//绘制y坐标(在右侧绘制)
//先计算yb_hist中的最大值
double minv[3], maxv[3]; //最小值,最大值
Point minLoc, maxLoc; //最小值位置,最大值位置
double allmax = 0;
for (int i = 0; i < 3; i++){
//y_hist在此使用
minMaxLoc(mv[i], &minv[i], &maxv[i], &minLoc, &maxLoc, Mat()); //只针对单通道图像
allmax += maxv[i];
}
//此处使用三通道直方图最大值的平均值作为纵坐标
double averMax = (allmax / 3);
//将最大值与图像的高求比例
int yb_h = cvRound(averMax / (double)hist_h);
for (int j = 1; j < hist_h; j++){
if ((j) % 20 == 0){
//绘制刻度线
line(histImage, Point(511, hist_h - j),
Point(517, hist_h - j), Scalar(255, 255, 0), 2, 8, 0);
//绘制数字
string stri = to_string(yb_h*j);
putText(histImage, stri, Point(517, hist_h - j), FONT_HERSHEY_COMPLEX, 0.4, Scalar(255, 255, 255), 1, 8);
}
}
//显示直方图
namedWindow("Histogram Demo", WINDOW_AUTOSIZE);
imshow("Histogram Demo", histImage);
}
else if (image.channels()==1)
{
//定义参数变量
const int channels[1] = { 0 };
const int bins[1] = { 256 };
float hranges[2] = { 0, 255 };
const float* ranges[1] = { hranges };
Mat hist;
//计算Blue,green,red通道的直方图
calcHist(&image, 1, 0, Mat(), hist, 1, bins, ranges);
//参数1;输入图像地址,参数2:1张图片(指针可以指向多个图),参数3:0表示一个通道,参数4:mask使用Mat()空计算整张图像,
//参数5:输出,参数6:1维,参数7:bins(256个灰度级别),参数8;ranges直方图范围
//将y_hist复制一下,在后面绘制y坐标时使用(防止中间hist值有修改)
Mat y_hist = hist.clone();
//opencv的从数据到图像需要自己绘制
//显示直方图
int hist_w = 512;
int hist_h = 400;
int bin_w = cvRound((double)hist_w / bins[0]); //cvRound():返回跟参数最接近的整数值,即四舍五入;
Mat histImage = Mat::zeros(408, 560, CV_8UC3);
//归一化直方图数据(三个通道计算的最小最大值范围不同,将其归一化到一个范围之间(画布高内),显示的时候不会超出画布范围)
normalize(hist, hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
//绘制直方图曲线(在一个mat图像上使用line绘制)
for (int i = 1; i < bins[0]; i++){
line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(hist.at<float>(i - 1))),
Point(bin_w*(i), hist_h - cvRound(hist.at<float>(i))), Scalar(12, 23, 200), 2, 8, 0);
}
//绘制x坐标
for (int i = 0; i < bins[0]; i++){
if ((i) % 15 == 0){
//绘制刻度线
line(histImage, Point(bin_w*(i), 396),
Point(bin_w*(i), 390), Scalar(255, 255, 0), 2, 8, 0);
//绘制数字
string stri = to_string(i);
putText(histImage, stri, Point(bin_w*(i), 405), FONT_HERSHEY_COMPLEX, 0.4, Scalar(255, 255, 255), 1, 8);
}
}
//绘制y坐标(在右侧绘制)
//先计算yb_hist中的最大值
double minv, maxv; //最小值,最大值
Point minLoc, maxLoc; //最小值位置,最大值位置
double allmax = 0;
//y_hist在此使用
minMaxLoc(y_hist, &minv, &maxv, &minLoc, &maxLoc, Mat()); //只针对单通道图像
//将最大值与图像的高求比例
int yb_h = cvRound(maxv / (double)hist_h);
for (int j = 1; j < hist_h; j++){
if ((j) % 20 == 0){
//绘制刻度线
line(histImage, Point(511, hist_h - j),
Point(517, hist_h - j), Scalar(255, 255, 0), 2, 8, 0);
//绘制数字
string stri = to_string(yb_h*j);
putText(histImage, stri, Point(517, hist_h - j), FONT_HERSHEY_COMPLEX, 0.4, Scalar(255, 255, 255), 1, 8);
}
}
//显示直方图
namedWindow("Histogram Demo", WINDOW_AUTOSIZE);
imshow("Histogram Demo", histImage);
}
}
//025-二维直方图(calcHist,minMaxLoc)
void quickDemo::histogram_2d_demo(Mat &image){
//2D直方图
Mat hsv, hs_hist;
cvtColor(image, hsv, COLOR_BGR2HSV);
int hbins = 30, sbins = 32;
int hist_bins[] = { hbins, sbins };
float h_range[] = { 0, 180 };
float s_range[] = { 0, 256 };
const float* hs_ranges[] = { h_range, s_range };
int hs_channels[] = { 0, 1 };
calcHist(&hsv, 1, hs_channels, Mat(), hs_hist, 2, hist_bins, hs_ranges, true, false);
//图像地址,1张图像,求前两个通道,对整张图像mask计算,输出结果hs_hist,2维结果,第一通道取值范围hist_bins,第二通道取值范围hs_ranges
double maxVal = 0;
minMaxLoc(hs_hist, 0, &maxVal, 0, 0);
int scale = 10;
Mat hist2d_image = Mat::zeros(sbins*scale, hbins*scale, CV_8UC3);
//此处绘制的是灰度值
for (int h = 0; h < hbins; h++){
for (int s = 0; s < sbins; s++){
float binVal = hs_hist.at<float>(h, s);
int intensity = cvRound(binVal * 255 / maxVal);
rectangle(hist2d_image, Point(h*scale, s*scale),
Point((h + 1)*scale - 1, (s + 1)*scale - 1),
Scalar::all(intensity),
-1);
}
}
imshow("H-S Histogram", hist2d_image);
applyColorMap(hist2d_image, hist2d_image, COLORMAP_JET); //将上面的图像结果用彩色的显示
imshow("H-S Histogram2", hist2d_image);
}
//026 - 直方图均衡化(equalizeHist)
void quickDemo::histogram_eq_demo(Mat &image){
//对灰度图像进行均衡化
Mat gray;
//cvtColor(image, gray, COLOR_BGR2GRAY);
//imshow("gray", gray);
//对彩色图像的亮度进行均衡化处理
Mat dst;
if (image.channels() == 1){ //如果是单通道图像
equalizeHist(image, dst); //直接进行直方图均衡化
imshow("灰度图像均衡化演示", dst);
}
else if (image.channels() == 3){ //如果是三通道图像
Mat YUV;
cvtColor(image, YUV, COLOR_BGR2YUV); //先将其转换成YUV空间(里面包含亮度通道)
//先进行通道分离,将三通道分离成3个单通道
Mat aChannels[3]; //此处必须为3,否则会报错
//src为要分离的Mat对象
split(YUV, aChannels); //利用数组分离
//Mat aY = aChannels[0]; //此处对应单通道Y(亮度)
//Mat aU = aChannels[1]; //此处对应单通道U(色差)
//Mat aV = aChannels[2]; //此处对应单通道V(色差)
//对亮度通道进行直方图均衡化
equalizeHist(aChannels[0], aChannels[0]);
//合并通道并将值赋给YUV
merge(aChannels, 3, YUV); //合并通道
//将YUV空间再次转换成BGR
cvtColor(YUV, dst, COLOR_YUV2BGR);
imshow("彩色图像HSV均衡化演示", dst);
}
}
//027-图像卷积操作-均值模糊(blur)
void quickDemo::blur_demo(Mat &image){
Mat dst;
blur(image, dst, Size(3, 3), Point(-1, -1));
//输入图像,输出图像,卷积核大小,锚点为中心,边缘处理方式默认
imshow("图像模糊", dst);
blur(image, dst, Size(15, 1), Point(-1, -1));
imshow("图像模糊-水平方向", dst);
blur(image, dst, Size(1, 15), Point(-1, -1));
imshow("图像模糊-垂直方向", dst);
}
//028-高斯模糊(GaussianBlur)
void quickDemo::gaussian_blur_demo(Mat &image){
Mat dst;
GaussianBlur(image, dst, Size(5, 5),15); //默认情况下,sigmax等于sigmay,只设置x的话;
//当Size(0,0)时,也会有模糊的效果(高斯模糊第3个参数卷积核大小必须是奇数,若为偶数会出错)
//当Size(0,0)时会通过sigma反算卷积核大小,若Size不为0,就会使用size的大小
//输入图像,输出图像,卷积核大小,sigmax,sigmay
imshow("高斯模糊", dst);
GaussianBlur(image, dst, Size(0, 0), 15); //sigma对卷积核的影响非常大,若设置Size(0,0),模糊会很大
imshow("高斯模糊2", dst);
}
//029-高斯双边模糊(bilateralFilter)
void quickDemo::bifilter_demo(Mat &image){
Mat dst;
bilateralFilter(image, dst, 0, 100, 20);
//参数解释:
// .InputArray src : 输入图像,可以是Mat类型,图像必须是8位或浮点型单通道、三通道的图像。
// .OutputArray dst : 输出图像,和原图像有相同的尺寸和类型。
// . int d : 表示在过滤过程中每个像素邻域的直径范围。如果这个值是非正数,则函数会从第五个参数sigmaSpace计算该值。
// . double sigmaColor : 颜色空间过滤器的sigma值,这个参数的值月大,表明该像素邻域内有月宽广的颜色会被混合到一起,产生较大的半相等颜色区域。
// . double sigmaSpace : 坐标空间中滤波器的sigma值,如果该值较大,则意味着颜色相近的较远的像素将相互影响,从而使更大的区域中足够相似的颜色获取相同的颜色。当d > 0时,d指定了邻域大小且与sigmaSpace五官,否则d正比于sigmaSpace.
// . int borderType = BORDER_DEFAULT: 用于推断图像外部像素的某种边界模式,有默认值BORDER_DEFAULT
imshow("高斯双边模糊", dst);
}
//030-案例:实时人脸检测(采用dnn模块)
void quickDemo::face_detection_demo(){
string root_dir = "D:/OpenCV/opencv-4.4.0-vc14_vc15/opencv/sources/samples/dnn/face_detector/";//此处最后要加入/否则会报错
dnn::Net net = dnn::readNetFromTensorflow(root_dir+"opencv_face_detector_uint8.pb",root_dir+"opencv_face_detector.pbtxt"); //读取神经网络(模型本质是tensflow的模型),读取模型和配置文件
//VideoCapture capture("G:/OpenCV/opencv所用视频/data_src.mp4"); //从指定路径中读取视频
VideoCapture capture(0);
if (!capture.isOpened())
{
std::cout << "Read video Failed !" << std::endl;
getchar();
return;
}
Mat frame;
while (true)
{
capture.read(frame); //将视频读取到Mat矩阵中
if (frame.empty())
{
break;
}
Mat blob = dnn::blobFromImage(frame,1.0,Size(300,300),Scalar(104,177,123),false, false); //这是一个输入,一个张量,blobFromImage可以转换,scalefactor1.0表示还在0-255的色彩空间,若0.0384,就变成0-1了;
//在dnn下的models.yml文件有默认取值,不进行通道交换,不进行剪切
//默认得到的数据是浮点类型的数据
net.setInput(blob); //input后就可以获取数据了 blob就是NCHW(N多少个,C通道数,H高度,W宽度)
Mat probs = net.forward(); //进行推理
Mat detectionMat(probs.size[2], probs.size[3], CV_32F, probs.ptr<float>());
//检测已完成
//解析结果
for (int i = 0; i < detectionMat.rows; i++) {
//(解析7个值,前两个类型,和index,第三个是得分,得分越高越可能是人脸,将其取出,也是个float数据)
float confidence = detectionMat.at<float>(i, 2);
if (confidence > 0.5) { //默认得分大于0.5它就是人脸了
int x1 = static_cast<int>(detectionMat.at<float>(i, 3)*frame.cols);//预测的值是0-1之间的数,只有乘宽度才会变成真实的
int y1 = static_cast<int>(detectionMat.at<float>(i, 4)*frame.rows);
int x2 = static_cast<int>(detectionMat.at<float>(i, 5)*frame.cols);//预测的值是0-1之间的数,只有乘宽度才会变成真实的
int y2 = static_cast<int>(detectionMat.at<float>(i, 6)*frame.rows);
//此时就得到矩形4个点的宽高了
Rect box(x1, y1, x2-x1, y2 - y1);
rectangle(frame, box, Scalar(0, 0, 255), 2, 8, 0); //绘制矩形得到人脸检测
}
}
imshow("人脸检测演示", frame);
//flip(frame, frame, 1);//电脑摄像头的视频有镜像,将其再反转一下
//TODO:do something...(可以在此处做一些对图像的操作)
//当按键为ESC时退出循环
int c = waitKey(1);
if (c == 27)
{
break;
}
}
//资源释放
capture.release();
}
main.cpp
#include "quickDemo.h"
int main(){
Mat src = imread("G:/OpenCV/opencv笔记所用图片/bandian.jpg",1); //默认加载彩色图像,0表示GRAY灰度图,1为BGR彩色图,-1表示加载原图(可能是其他类型如HSV等其他空间)
if (src.empty()){
cout << "could not load image..." << endl;
getchar();
return -1;
}
//当我们读取图片太大时,看不到全局,使用窗口函数可以设置大小
namedWindow("input", WINDOW_AUTOSIZE); //WINDOW_FREERATIO参数可以调整窗口大小。默认图像为WINDOW_AUTOSIZE显示原图,不能调整大小。
imshow("input", src); //若无namedWindow,只有imshow,显示的图像窗口与图片一样大,无法调整窗口大小
//imshow只能显示8位和浮点型的
quickDemo qd;
//qd.colorSpace_Demo(src); //002-色彩空间变换
//qd.mat_creation_demo(src); //003-图像对象的创建与赋值
//qd.pixel_visit_demo(src); //004-图像像素的读写操作
//qd.operators_demo(src); //005-图像像素的算术操作
//qd.trackbar_demo(src); //006-trackbar滚动条操作演示-调整图像亮度
//qd.trackbar_demo2(src); //007-trackbar滚动条操作演示-参数传递与调整亮度与对比度
//qd.key_demo(src); //008-键盘响应操作
//qd.color_style_demo(src); //009-opencv自带颜色表操作
//qd.bitwise_demo(src); //010-图像像素的逻辑操作(位操作,与、或、非)
//qd.channels_demo(src); //011-通道分离与合并
//qd.inrange_demo(src); //012-图像色彩空间转换-背景变换(cvtColor,inRange)
//qd.pixel_static_demo(src); //013-图像像素值统计(minMaxLoc,meanStdDev)
//qd.drawing_demo(src); //014-图像几何形状绘制(rectangle,Rect,circle,line,ellipse,RotatedRect)
//qd.random_drawing_demo(); //015-随机数与随机颜色
//qd.polyline_drawing_demo(); //016-多边形填充与绘制(fillPoly,polylines,drawContours)
//qd.mouse_drawing_demo(src); //017-鼠标操作与响应(setMouseCallback)
//qd.norm_demo(src); //018-图像像素类型转换与归一化(convertTo,normalize)
//qd.resize_demo(src); //019-图像缩放与插值(resize)
//qd.flip_demo(src); //020-图像翻转(flip)
//qd.rotate_demo(src); //021-图像旋转(getRotationMatrix2D,warpAffine)
//qd.video_demo(src); //022-视频文件摄像头使用(VideoCapture)
//qd.video_demo2(src); //023-视频处理与保存-帧宽高(capture.get)
//qd.showHistogram(src); //024-图像直方图-绘制直方图(calcHist,normalize,line)
//qd.histogram_2d_demo(src); //025-二维直方图(calcHist,minMaxLoc)
//qd.histogram_eq_demo(src); //026 - 直方图均衡化(equalizeHist)
//qd.blur_demo(src); //027-图像卷积操作-均值模糊(blur)
//qd.gaussian_blur_demo(src); //028-高斯模糊(GaussianBlur)
//qd.bifilter_demo(src); //029-高斯双边模糊(bilateralFilter)
qd.face_detection_demo(); //030-案例:实时人脸检测(采用dnn模块)
waitKey(0); //opencv自带阻塞函数,0表示一直阻塞,1表示延迟1毫秒后执行下一步
destroyAllWindows(); //结束程序前将所有窗口销毁
return 0;
}