文章目录
- 1.配置开发环境
- 2.图像读取与显示
- 3.图像色彩空间转换
- 4.图像对象的创建与赋值
- 5.图像像素的读写操作
- 6.图像像素的算数操作
- 7.滚动条-调整图像亮度
- 8.滚动条-调整对比度和亮度
- 9.键盘响应操作
- 10.图像像素的逻辑操作
- 11.图像的通道分离和合并
- 12.图像色彩空间转换
- 13.图像的像素值统计
- 14.图像几何形状绘制
- 15.随机数与随机颜色
- 16.多边形填充与绘制
- 17.鼠标操作与响应
- 18.图像像素类型转换与归一化
- 19.图像缩放与插值
- 20.图像翻转
- 21.图像旋转
- 22.视频文件摄像头使用
- 23.视频处理与保存
- 24.图像直方图
- 25.二维直方图
- 26.直方图均衡化
- 27.图像卷积和高斯模糊
- 28.双边模糊
- 29.人脸实时检测
1.配置开发环境
配置开发环境提前需要安装好Visual Studio和opencv包,这里可以单独观看视频学习安装。
以下过程参考链接: B站opencv快速入门30讲-贾志刚
-
配置包含目录
这里需要注意上面的菜单栏选择Release和x64,打开属性管理器选择Release|x64>VC++目录>包含目录。(可能由于Visual Studio版本的问题,这里没有和视频中相同的microsoft.cpp.x64.user文件(但是新建的相同文件名的文件会显示已存在该文件,在项目中确实已经存在),因此直接对Release|x64属性修改是一样的)
具体步骤:视图》其他窗口》属性管理器》Release | x64》属性》VC++目录》包含目录》编辑》将opencv安装包中的两个目录地址复制进去,如下图所示:这样包含目录就配置好了。
-
配置库目录
接下来配置库目录,点击常规下面的库目录》编辑》如下图所示将opencv安装包的lib 目录复制进去,点击确定。这样库目录就配置好了。
-
配置链接器
接下来配置链接器:如下图所示:属性》链接器》输入》附加依赖项》编辑》,在下图中有两个.lib 文件opencv_world460.lib 和opencv_world460d.lib,分别对应Release和Debug,切记勿将两个文件同时写进去。这里选择opencv_world460.lib。点击确定。这样链接器配置完毕。
-
配置环境变量并重启VS2022
系统》高级系统设置》配置环境变量,将opencv安装包里面的类似下图的bin路径添加进去,点击确定。重启VS。
配置环境变量并重启VS2022
2.图像读取与显示
c
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc, char ** argv){
cv::Mat src = cv::imread("C/code/workspace/lena.jpg", IMREAD_GRAYSCALE);
nameWindow("输入窗口", WINDOW_FREERATIO);
if (src.empty()) {
printf("could not load image...");
return -1;
}
cv::imshow("输入窗口", src);
cv::watiKey(0);
destoryAllWindows();
return 0;
}
代码解释:
#<opencv2/opencv.hpp> OpenCV库预处理指令,包含了OpenCV库的所用头文件。
C++标准库预处理指令。该头文件包含了C++库中的输入输出流函数。
using namespace cv 这是openCV库的命名空间。它将所有OpenCV库中的函数都放在cv命名空间中,这样可以通过cv:: 函数来调用它们。
suing namespace std 这是C++标准库的命名空间,它将C++库中的函数都放在std命名空间中,这样可以通过std:: 函数来调用它们。
int main(int argc, char** argv) 这是程序主函数接受两个参数,argc和argv,argc是一个整数,表示命令行参数的个数,argv是一个字符淑珍数组,表示命令行参数的指针。
cv::Mat src = cv::imread("C/code/workspace/lena.jpg", IMREAD_GRAYSCALE); cv::imread()是OpenCV读取图像的函数,将图像读取为灰度图像,并将图像存储在cv::Mat 的src变量中。
nameWindow("输入窗口",WINDOW_FREERATIO)这是OpenCV库中创建窗口的函数指令,将创建一个名为"输入窗口"窗口,并使用WINDOW_FREERATIO自动调整窗口大小。
cv::imshow("输入窗口", src) 这是OpenCV中显示图像的函数,它将变量src显示在"输入窗口"中。
cv::waitKey(0) 这是OpenCV中的waitKey函数等待用户按键的指令,它将等待用户按下任意键,然后继续执行。
destroyAllWindows() 这是OpenCV关闭所有窗口的指令。
3.图像色彩空间转换
这节学习如何创建头文件,如何定义头文件中的函数,以及如何在程序中调用自定义的头文件函数
-
头文件的创建
创建头文件:在项目文件下面的头文件中创建一个新建项,名为xxxxx.h 的文件c#include <opencv2/opencv.hpp> using namespace cv; class QuickDemo { public: void colorSpace_Demo(Mat& image); };
代码解释:定义了一个名为QuickDemo 的类,public 是类的公共部分的开始, void colorSpace_Demo( )是QuickDemo类的成员函数,这个函数接受一个Mat类型的参数 image,用于存储输入的图像。};是类的定义的结束。
-
定义头文件函数
头文件中定义的类成员函数需要在源文件中创建一个名为 xxxxx.cpp 的文件c#include "quickopencv.h" void QuickDemo::colorSpace_Demo(Mat& image) { Mat gray, hsv; cvtColor(image, hsv, COLOR_BGR2HSV); cvtColor(image, gray, COLOR_BGR2GRAY); imshow("HSV", hsv); imshow("灰度", gray); imwrite("C/code/workspace/hsv.jpg", hsv); imwrite("C/code/workspace/gray.jpg", gray); }
代码解释:QuickDemo::colorSpace_Demo(Mat& image)): 这是QuickDemo类的成员函数colorSpace_Demo的声明。cvtColor() 是OpenCV库进行颜色空间转化的指令。imwrite()这是OpenCV的imwrite函数将图像保存到文件的指令。
HSV(Hue, Saturation, Value)是一种颜色的表示方式,色相(Hue),饱和度(Saturation), 透明度(Value) -
主函数文件
主函数文件在源文件中 新建项为文件名 xxxxxx.cpp 的文件c#include <opencv2/opencv.hpp> #inluce <iostream> #include "quickopencv" using namespace cv; using namespace std; int main(int argc, char ** argv){ Mat src = cv::imread("C/code/workspace/lena.jpg"); nameWindow("输入窗口", WINDOW_FREERATIO); if(src.empty()) { printf("could not load image..."); return -1; } cv::imshow("输入窗口",src); QuickDemo qd; qd.colorSpace_Demo(src); cv::waitKey(0); destroyAllWindows(); return 0; }
4.图像对象的创建与赋值
这一节承接上一节的内容,需要在自定义的头文件中添加类QuickDemo 的成员函数mat_creation_demo(), 同时需要在源文件夹下面的quickdemo.cpp 中定义函数Quick::mat_creation_demo() 的具体内容。本节中学习的关于OpenCV库中的克隆、复制、赋值、创建空白图像等都是在成员函数mat_creation_demo()中定义的。在执行程序时还需要再main.cpp 文件中调用mat_creation_demo()函数。
下面是关于成员函数的定义:
c
void QuickDemo::mat_creation_demo(Mat& image){
Mat src = image;
// 创建方法-克隆
Mat m1 = src.clone();
//复制
Mat m2;
src.copyTo(m2);
//赋值法
Mat m3 = srcl;
//创建空白图像
Mat m4 = Mat::zeros(src.size(), src.type());
Mat m5 = Mat::zeros(size(512, 512), CV_8UC3);
imshow("窗口1", m1);
imshow("窗口2", m2);
imshow("窗口3", m3);
// 除上面的图像对象外,还可以创建一个空白对像,然后赋值标量
Mat m6, m7;
m6 = image.clone();
image.copyTo(m7);
//创建单通道的空白图像
Mat m8 = Mat::zeros(Size(512, 512), CV_8UC1);
//创建三通道空白图像
Mat m9 = Mat::zeros(Size(512, 512), CV_8UC3);
//给三通道空白图像赋标量值
m9 = Scalar(0, 128, 64);
std::cout << "width:" << m9.cols << "height:" << m9.rows << "channels:" << m9.channels() << std::endl;
std::cou t << m9 <<std::endl;
代码解释:上面的代码中主要是最后两行的输出流需要注意,m9.cols:这是m9 对象的成员变量,表示图像的宽度。m9.rows:这是m9 对象的成员变量,表示图像的高度。m9.channels():这是m9 对象的成员变量,用于获取图像的通道数。std::cout:这是C++标准库中的iostream流对象,用于输出文本,<< :这是流对象的输出操作符,用于将右边的操作数(如变量m9.cols)输出到流中。std::endl:这是iostream流对象的结束标记,用于输出一个换行符,并刷新输出缓冲区。
5.图像像素的读写操作
第一种方法是通过数组坐标访问每一像素,总体来看是使用了两个for循环;同样的,这里是在类成员是函数的详细定义中,去定义这个像素的访问。下面是第一种方法的代码演示:
c
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<uchar>(row, col);
image.at<uchar>(row, col) = 255 - pv;
}
if (dims == 3) { //彩色图像
Vec3b bgr = image.at<Vec3b>(row, col);
image.at<Vec3b>(row, col)[0] = 255 - bgr[0];
image.at<Vec3b>(row, col)[0] = 255 - bgr[1];
image.at<Vec3b>(row, col)[0] = 255 - bgr[2];
}
}
}
imshow("像素读写演示", image);
第二种方法是通过指针的方式访问每个像素,下面是代码演示:
c
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++) {
uchar* current_row = image.ptr<uchar>(row);
for (int col = 0; col < w; col++) {
if (dims == 1) { //灰度图像
int pv = *current_row;
*current_row++ = 255 - pv;
}
if (dims == 3) { //彩色图像
*current_row++ = 255 - *current_row;
*current_row++ = 255 - *current_row;
*current_row++ = 255 - *current_row;
}
}
}
imshow("像素读写演示", image);
代码解释:这里主要是需要理解指针的运用,image.ptr(row):这是OpenCV中的Mat对象image的成员函数ptr,它返回抑制指向图像第row行的数据的指针。表示我们想要获取的像素数据类型是8位无符号整数(uchar).插入一句额外的话,C语言之所以效率高,就是因为C可以像汇编一样去操控内存。整形(int)是占用4个字节,基于32位的有符号整形。
下面是关于指针的代码图解
6.图像像素的算数操作
这节学习如何对图像的像素进行加减乘除操作,下面通过代码演示加深对图像像素进行加减乘除的理解。
c
void QuickDemo::operators_demo(Mat &image){
Mat dst = Mat::zeros(image.size(), image.type());
Mat m = Mat::zeros(image.size(), image.type());
//dst = image - Scalar(50, 50, 50);
//dst = image + Scalar(50, 50 ,50);
//mutiply(image, m, dst);
// dst = image / Scalar(50, 50, 50);
// imshow("图像的算数操作", dst);
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++){
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]);
dst.at<Vec3b>(row, col)[1] = saturate_cast<uchar>(p1[1] + p2[1]);
dst.at<Vec3b>(row, col)[2] = saturate_cast<uchar>(p1[2] + p2[2]);
}
}
imshow("加法操作", dst);
}
代码解释:at(row, col)表示获取指定位置的像素,获得的p1 是一个有三个元素的数组,saturate_cast是将相加后的像素值转换为8位无符号(uchar)的整数,以适应dst图像的像素范围。
7.滚动条-调整图像亮度
这节学习通过滚动条,用户可以在窗口中自行调整图像的亮度,下面是代码演示:
c
Mat src, dst, m;
int lightness = 20;
static void on_track(int, void*) {
m = Scalar(lightness, lightness, lightness);
add(src, m, dst);
//subtract(src, m, dst);
imshow("亮度调整", dst);
}
void QuickDemo::tracking_bar_demo(Mat& image) {
namedWindow("亮度调整", WINDOW_AUTOSIZE);
dst = Mat::zeros(image.size(), image.type());
m = Mat::zeros(image.size(), image.type());
src = image;
int max_value = 250;
createTrackbar("Value Bar:", "亮度调整", &lightness, max_value, on_track);
on_track(50, 0);
}
代码解释:这节的代码和下一节一起进行解释。
8.滚动条-调整对比度和亮度
这节学习在窗口中添加两个滚动条,一个调整图像的亮度,另一个调整图像的对比度,下面是代码演示:
c
static void on_lightness(int b, void* userdata){
Mat image = *((Mat*)userdata);
Mat dst = Mat::zeros(image.size(), image.type());
Mat m = Mat::zeros(image.size(), image.type());
addWeighted(image, 1.0, m, 0, b, dst);
imshow("亮度与对比度调整", dst);
}
static void on_contrast(int b, void* userdata){
Mat image = *((Mat*)userdata);
Mat dst = Mat::zeros(image.size(), image.type());
Mat m = Mat::zeros(image.size(), image.type());
double contrast = b / 100.0;
addWeighted(image, contrast, m, 0.0, 0, dst);
imshow("亮度与对比度调整", dst);
}
void QuickDemo::tracking_bar_demo(Mat &image){
nameWindow("亮度与对比度调整", WINDOW_AUTOSIZE);
int lightness = 50;
int max_value = 250;
int contrast_value = 100;
createTrackbar("Value Bar:", "亮度与对比度调整", &lightness,max_value, on_lightness, (void*)(&image));
createTrackbar("Contrast Bar:", "亮度与对比度调整", &contrast_value,200, on_contrast, (void*)(&image));
on_lightness(50, &image);
}
代码解释:在回调函数on_lightness()中,static: 表示该函数是静态的,只在当前文件中可见。除此之外,还有静态变量,静态函数,静态类。定义了变量的周期,在静态函数中,只在当前文件中可见,防止其他文件有重名的函数。void:表示函数的返回类型为空;on_lightness:是函数名;int b:表示亮度调整参数;void* userdata:一个指向用户数据的指针,通常用于传递图像数据;
((Mat )userdata): 将userdata指针转换为Mat* 类型并解引用,获取图像数据。addWeighted(image, 1.0, m, 0, b, dst); addWeighted: OpenCV中的函数,用于图像的加权叠加。该函数的公式为:dst = image * 1.0 + m * 0 + b,即 dst = image + b,实现了亮度的调整。
最后定义了一个名为tracking_bar_demo的函数,用于创建一个窗口并添加了两个滑动条(Tackbar),分别用于调整图像的亮度和对比度。createTrackbar: OpebCV中的函数,用于创建一个滑动条。Value Bar: :滑动条的标签。&lightness: 指向亮度值的指针,滑动条的当前值。max_value: 滑动条的最大值。on_lightness: 回调函数,当滑动条值改变时调用。(void*)(&image): 传递给回调函数的用户数据,即图像数据
9.键盘响应操作
这节学习通过键盘按键对图像进行操作并显示操作后的结果
c
void QuickDemo::key_demo(Mat& image) {
Mat dst = Mat::zeros(image.size(), image.type());;
while (true) {
int c = waitKey(100);
if (c == 27) {//Esc
break;
}
if (c == 49) {//Key#1
std::cout << "you enter Key#1" << std::endl;
cvtColor(image, dst, COLOR_BGR2GRAY);
}
if (c == 50) {// Key#2
std::cout << "you enter Key#2" << std::endl;
cvtColor(image, dst, COLOR_BGR2HSV);
}
if (c == 51) {//Key#3
std::cout << "you enter Key#3" << std::endl;
dst = image + Scalar(0, 128, 0);
}
//std::cout << c << std::endl;
imshow("键盘响应", dst);
}
}
代码解释:需要注意的是函数waitKey()返回的数据类型,总体上代码不难。
10.图像像素的逻辑操作
这节学习创建两个图像,对这两个图像进行与、或、非 操作;下面是代码演示:
c
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, LINE_8, 0);
rectangle(m2, Rect(150, 150, 80, 80), Scalar(0, 255, 255), -1, LINE_8, 0);
imshow("m1", m1);
imshow("m2", m2);
Mat dst;
//Mat dst = ~image;
//bitwise_not(image, dst);
//bitwise_or(m1, m2, dst);
bitwise_xor(m1, m2, dst);
imshow("像素位操作", dst);
}
代码解释:rectangle() 是OpenCV库中的函数,在图像上绘制一个填充的矩形。m1表示目标图像,即在m1上进行绘制,Rect() 这是矩形的参数,用于确定矩形的大小和位置;Scalar()这是矩形的颜色,-1表示矩形的厚度,这里就表示对矩形进行填充,除此之外还有其他参数,例如0,1,2;LINE_8这是绘制矩形时使用的线条类型,LINE_8表示8连通线型。0:这是可选的坐标缩放因子。
11.图像的通道分离和合并
这节学习将RGB图像的三个通道分离,并以单独的红色,绿色和蓝色进行显示;这是通道分离,通道合并时,学习如何将两个任意的通道进行合并并显示出来。下面时代码演示:
c
void QuickDemo::channels_split_demo(Mat& image) {
std::vector<Mat> mv;
split(image, mv);
imshow("蓝色", mv[0]);
imshow("绿色", mv[1]);
imshow("红色", mv[2]);
Mat dst;
mv[1] = 0;
mv[2] = 0;
merge(mv, dst);
imshow("蓝色", dst);
int from_to[] = { 0, 2, 1, 1, 2, 0 };
mixChannels(&image, 1, &dst, 1, from_to, 3);
imshow("通道混合", dst);
}
代码解释:split() 函数用于将图像分离成三个通道并存储在mv变量中,这样根据数组的操作可以对单独的每个通道进行显示,merge() 函数用于将两个图像进行合并,合并前将其余的两个通道的数值置零就可以以单独的红、蓝、绿进行显示;最后用到一个通道混合的函数mixChannels(),mixChannels是OpenCV库中的一个函数,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:一个数组,指定输入和输出通道的映射关系。 size_t npairs是数组中映射对的数量。
12.图像色彩空间转换
这节学习如何从纯色的背景中扣出前景然后对背景颜色进行转换。需要注意的是背景颜色必须是纯色的。下面是代码演示以及实验结果图。
c
void QuickDemo::inrange_demo(Mat& image){
Mat hsv;
cvtColor(image, hsv, COLOR_BGR2HSV);
Mat mask;
inRange(hsv, Scalar(36, 43, 46), Scalar(77, 255, 255), mask);
imshow("mask", mask);
Mat redback = Mat::zeros(image.size(), image.type());
redback = Scalar(40, 40, 200);
bitwise_not(mask, mask);
imshow("mask", mask);
image.copyTo(redback, mask);
imshow("roi区域提取", redback);
}
代码解释:inRange()是OpenCV库中的函数,用于在HSV颜色空间中进行颜色阈值处理。具体来说,它将图像中的像素值与给定的范围进行比较,并将符合条件的像素设置为白色(255), 不符合条件的像素设置为黑色(0)。HSV颜色空间将颜色分解为色调(Hue)、饱和度(Saturation)和亮度(Value)三个分量;在上述代码中hsv是输入的图像,Scalar(36, 43, 46): 是下限阈值,这里的阈值根据HSV颜色空间能够进行查询获得,这里的三个数是绿色的阈值下限。Scalar(77, 255, 255)是绿色的阈值上限。mask:输出图像,是一个二值化的图像,在这个图像中符合颜色范围的像素值为255,不符合的像素值为0。后面将掩码取反操作,即前景图像的像素值从原来的0变为255,背景像素值从原来的255变为0;接下来使用image.copyTo(redback, mask);这里使用掩码复制掩码中像素值为255的redback像素值。这里更深入的理解需要理解copyTo()函数。
13.图像的像素值统计
这节学习如何计算图像各通道的均值和方差。下面是代码演示:
c
void QuickDemo::pixel_statistic_demo(Mat& image) {
double minv, maxv;
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());
std::cout <<"No.channels: "<< i <<"min value : " << minv << "max value : " << maxv << std::endl;
}
Mat mean, stddev;
meanStdDev(image, mean, stddev);
std::cout << "means:" << mean << std::endl;
std::cout<< "stddev:" << stddev << std::endl;
}
代码解释:首先定义了两个Point类型的变量分别命名为minLoc, mavLoc;;其次定义了一个std::vector类型的变量mv,其元素类型为Mat ,这里的mv可以用于存储多个图像或矩阵数据。.Point是OpenCV库中定义的一个类,通常用于表示图像中的二维坐标点(x, y)。minMaxLoc()是OpenCV库中的函数,用于在给定的图像或矩阵中查找最小值和最大值及其位置。minMaxLoc(mv[i], &minv, &maxv, &minLoc, &maxLoc, Mat()); ,mv[i]:是输入的数据,&minv这是指向double类型的指针,用于存储找到的最小值。&minLoc这是指向Point类型的指针,用于存储最小值的位置,Mat(): 这是一个空的Mat对象,表示掩码(mask).如果不需要使用掩码,可以传递一个空的Mat对象。掩码用于指定在哪些区域中进行最小值和最大值的查找。meanStdDev是OpenCV中的函数。用于计算给定图像的均值和标准差。
14.图像几何形状绘制
这节学习在图像上绘制或者填充矩形、圆形、椭圆形、线条等,下面是代码演示:
c
void QuickDemo::darwing_demo(Mat& image) {
Rect rect;
rect.x = 250;
rect.y = 150;
rect.width = 100;
rect.height = 100;
Mat bg = Mat::zeros(image.size(), image.type());
rectangle(image, rect, Scalar(0, 0, 255), 2, 8, 0);
circle(image, Point(350, 400), 50, Scalar(255, 0, 0), -1, 8, 0);
line(image, Point(100, 100), Point(200, 200), Scalar(255, 255, 0));
RotatedRect rrt;
rrt.center = Point(200, 200);
rrt.size = Size(100, 200);
rrt.angle = 90;
ellipse(image, rrt, Scalar(0, 0, 255), 2, 8);
imshow("矩形", image);
}
代码解释:Rect是OpenCV库中定义的一个类,用于表示矩形区域。Rect rect这行代码声明了一个Rect类型的变量。rectangle() 函数用于在图像上绘制一个矩形。2: 表示矩形边框的厚度,如果设置为负数矩形会被填充。8表示8连接线,0表示旋转角度。circle() 函数用于在图像上绘制一个圆形。Point(100, 100): 这是Point类型的对象,表示圆心的位置。50:表示圆的半径。line() 函数用于在图像上绘制一条直线。 RotatedRect是OpenCV库中的函数用来绘制椭圆。
15.随机数与随机颜色
这节学习如何生成随机数并且根据随机种子在创建的图像中画随机线条。下面是代码演示:
c
void QuickDemo::random_drawing() {
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(100);
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);
}
}
代码解释:RNG rng(12345):这行代码创建了一个随机数生成器对象rng,并使用12345作为随机种子,随机种子是一个初始值,用于初始化随机数生成器的状态,相同的种子会产生相同的随机数序列。随机种子的作用是确保每次运行程序时,如果使用相同的种子,生成的随机数序列是相同的。可以确保每次运行程序时生成的随机数序列是可重复的。
16.多边形填充与绘制
这节学习如何绘制多边形:下面是代码是演示:
C
void QuickDemo::polyline_drawing_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);
std::vector<Point>pts;
pts.push_back(p1);
pts.push_back(p2);
pts.push_back(p3);
pts.push_back(p4);
pts.push_back(p5);
//polylines(canvas, pts, true, Scalar(0, 0, 255), 8, 8, 0);
//fillPoly(canvas, pts, Scalar(255, 255, 0), LINE_AA, 0);
std::vector<std::vector<Point>>contours;
contours.push_back(pts);
drawContours(canvas, contours, -1, Scalar(255, 0, 0), -1);
imshow("多边形绘制", canvas);
}
代码解释:pts.push_back()将五个点添加到向量pts中,polyline() 函数是OpenCV中用于绘制多边形的函数,canvas:绘制画布。pts:点向量;true:表示多边形是闭合的。fillPoly函数用于填充多边形。drawContours()函数是OpenCV库中绘制轮廓的函数。
17.鼠标操作与响应
这节学习通过鼠标绘制矩形提取图像中的感兴趣区域(ROI),下面是代码演示:
c
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;
std::cout<<"start point:"<<sp<<sts::endl;
}
else if(event == EVENT_LBUTTONUP){
ep.x = x;
ep.y = y;
int dx = ep.x - ep.x;
int dy = ep.y - ep.y;
if(dx>0 && dy>0){
Rect box(sp.x, sp.y, dx, dy);
rectangle(image, box, Scalar(0, 0, 255), 2, 8, 0);
imshow("鼠标绘制", image);
imshow("ROI区域", image(box));
}
else if(event ==EVENT_MOUSEMOVE){
if(sp.x>0 && sp.y>0){
ep.x = x;
ep.y = y;
int dx = ep.x - ep.x;
int dy = ep.y - ep.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);
inshow("鼠标绘制", image);
}
}
}
}
void QuickDemo::mouse_drawing_demo(Mat& image){
nameWindow("鼠标绘制", WINDOW_AUTOSIZE);
setMouseCallback("鼠标绘制", on_draw, (void*)(&image));
imshow("鼠标绘制", image);
temp = image.clone();
}
代码解释:static void on_draw(int event, int x, int y, int flags, void* userdata){ 这行代码定义了一个名为 on_draw的静态函数,该函数在绘制事件发生时被调用。函数的参数包括:event: 表示绘制事件类型。当用户点击鼠标左键时,事件类型为EVENT_LBUTTONDOWN。flags:表示事件标志,userdata: 表示用户数据,在这个例子中是一个指向Mat类型的指针,表示要绘制图像。void QuickDemo::mouse_drawing_demo(Mat& image){ 这行代码定义了一个名为mouse_drawing_demo的函数,该函数用于实现鼠标绘制的功能,setMouseCallback()函数是OpenCV库中的函数,为鼠绘制窗口设置鼠标回调函数。on_draw是回调函数的名称,(void*)(&image)是将图像地址传递给回调函数,以便在回调函数中使用。
18.图像像素类型转换与归一化
这节学习将图像通整数类型转换为浮点数数类型并进行显示
c
void QuickDemo::norm_demo(Mat& image) {
Mat dst;
std::cout << image.type() << std::endl;
image.convertTo(image, CV_32F);
std::cout << dst.type() << std::endl; // CV_8UC3, CV_32FC3
normalize(image, dst, 1.0, 0, NORM_MINMAX);
std::cout << dst.type() << std::endl;
imshow("图像数据归一化", dst);
}
代码解释:这段代码首先将图像通过.convertTo()函数转换为32位浮点数类型。然后通过 归一化函数normlize()将图像归一化并存储在dst中,归一化的方式是NORM_MINMAX,最后显示归一化后的图像数据dst。
19.图像缩放与插值
这节学习图像的缩放和插值操作,内容相对简单。下面是代码演示:
c
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", zoomin);
resize(image, zoomout, Size(w * 1.5, h * 1.5), 0, 0, INTER_LINEAR);
imshow("zoomout", zoomout);
}
代码解释:这里记住resize()函数就可以实现对图像的缩放。
20.图像翻转
这节学习图像的翻转,只需要使用flip() 函数改变其参数就能实现图像的水平翻转、上下翻转、对角线翻转。代码相对简单。
c
void QuickDemo::flip_demo(Mat& image) {
Mat dst;
//flip(image, dst, 0);//上下翻转
//flip(image, dst, 1);//左右翻转
flip(image, dst, -1); //对角线翻转,180°旋转
imshow("图像翻转", dst);
}
21.图像旋转
这节学习对图像实现旋转,图像旋转是通过仿射变换实现的,图像旋转后的高宽会发生变换,新的高宽通过下面图示方式进行计算;
下面是代码演示:
c
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);
double cos = abs(M.at<double>(0, 0));
double sin = abs(M.at<double>(0, 1));
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.at<double>(1, 2) = M.at<double>(1, 2) + (nh / 2 - h / 2);
warpAffine(image, dst, M, Size(nw, nh), INTER_LINEAR, 0, Scalar(255, 255, 255));
imshow("旋转演示", dst);
}
代码解释:getRotationMatrix2D()是OpenCV库中的函数,它的功能是生成一个二维的旋转矩阵,Point2f类型的对象,表示旋转的中心点,45表示旋转的角度,1.0表示旋转的缩放因子。M是旋转矩阵,从旋转矩阵中取出cos seta 和 sin seta,接下来重新计算高宽,然后将旋转中心值重新赋值给M矩阵,组后通过warpAffine()函数进行旋转放射变换。
22.视频文件摄像头使用
这节学习如何调用电脑摄像头实时显示摄像头内容以及如何读取视频文件,对读取到的视频内容也可以使用图像处理中的一些处理进行显示,例如灰度变换、HSV、抠图等。下面是代码演示:
c
void QuickDemo::video_demo(Mat& image) {
VideoCapture capture(0); // 读出视频文件capture(../video/xxx.mp4)
Mat frame;
while (true) {
capture.read(frame);
flip(frame, frame, 1);
if (frame.empty()) {
break;
}
imshow("视频", frame);
//TODO: do something...
int c = waitKey(10);
if (c == 27) {
break;
}
}
//release
capture.release();
}
代码解释:VideoCapture是OpenCV中用于视频捕获的类,capture(0)表示从默认摄像头捕获视频,Mat是OpenCV中用于存储图像数据的类。frame是一个Mat对象,用于存储图像数据的类。
23.视频处理与保存
这节学习如何获取读取到的视频属性,例如高宽、视频帧总数、每秒的帧数(FPS)等。最后学习如何保存视频。下面是代码演示:
c
void QuickDemo::video_demo(Mat& image) {
VideoCapture capture("C:/code/workspace/data/video/dance.mp4"); // 读出视频文件capture(../video/xxx.mp4) 读取摄像头capture(0)
int frame_width = capture.get(CAP_PROP_FRAME_WIDTH);
int frame_height = capture.get(CAP_PROP_FRAME_HEIGHT);
int count = capture.get(CAP_PROP_FRAME_COUNT);
double fps = capture.get(CAP_PROP_FPS);
std::cout << "fram width:" << frame_width << std::endl;
std::cout << "fram height:" << frame_height << std::endl;
std::cout << "Number of frames:" << count << std::endl;
std::cout << "FPS:" <<fps<< std::endl;
VideoWriter writer("C:/code/workspace/data/video/test.mp4", capture.get(CAP_PROP_FOURCC), fps, Size(frame_width, frame_height), true);
Mat frame;
while (true) {
capture.read(frame);
flip(frame, frame, 1);
if (frame.empty()) {
break;
}
namedWindow("视频", WINDOW_NORMAL);
resizeWindow("视频", 800, 600);
imshow("视频", frame);
//colorSpace_Demo(frame);
writer.write(frame);
//TODO: do something...
int c = waitKey(10);
if (c == 27) {
break;
}
}
//release
capture.release();
writer.release();
}
代码解释:获取视频的高宽等属性用capture.get()函数,保存视频文件时,VideoWriter是OpenCV库中用于视频写入的类。writer是VideoWriter对象的名称。capture是一个VideoCapture对象,用于视频捕获。write 是 VideoWriter 类的一个成员函数,用于将一帧图像写入到视频文件中。
24.图像直方图
这节学习绘制rgb图像三个通道的直方图曲线,并将它们可视化在一张画布上,下面是代码演示:
c
void QuickDemo::Histogram_demo(Mat& image){
//三通道分离
std::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);
//显示直方图
int hist_w = 512;
int hist_h = 400;
int bin_w = cvRound((double)hist_w / bins[0]); //每个bin的宽度
Mat histImage = Mat::zeros(histImage.rows, NORM_MINMAX, -1, Mat());
Mat histImage = Mat::zeros(histImage.rows, NORM_MINMAX, -1, Mat());
Mat histImage = Mat::zeros(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, 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(255, 0, 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(255, 0, 0), 2, 8, 0);
//显示直方图
nameWindow("Histogram Demo", WINDOW_AUTOSIZE);
imshow("Histogram Demo", histImage);
代码解释:cvRound是OpenCV中的一个函数,用于将浮点数四舍五入为最接近的整数。这里关于线段的起始坐标还没有搞清楚,
25.二维直方图
代码演示:
c
void QuickDemo::Histogram_2d_demo(Mat& image) {
//2D直方图
Mat hsv, hs_hist;
cvtColor(image, hsv, COLOR_BGR2HSV); // H取值范围是(0,180),S和V的取值范围是(0,255)
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);
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);
}
}
applyColorMap(hist2d_image, hist2d_image,COLORMAP_JET);
imshow("H-S Histogram", hist2d_image);
imwrite("C:/code/workplace/data/image/his2d.jpg", hist2d_image);
}
26.直方图均衡化
原理:统计直方图 》归一化直方图》累计直方图》区间转换
直方图均衡化只能对单通道灰度图像进行均衡化,如果对彩色图像进行均衡化可以将彩色图像转换到HSV色彩空间然后对V通道的亮度进行直方图均衡化。
c
void QuickDemo::histogram_eq_demo(Mat& image){
Mat gray;
cvtColor(image, gray, COLOR_BGR2GRAY);
Mat dst;
equalizeHist(gray, dst);
imshow("直方图均衡化", dst);
}
27.图像卷积和高斯模糊
这节学习对图像进行卷积操作和使用高斯核函数对图像进行模糊处理,下面是代码演示:
c
void QuickDemo::blur_demo(Mat& image) {
Mat dst;
blur(image, dst, Size(13, 13), Point(-1, -1));
imshow("图像模糊", dst);
}
void QuickDemo::gaussian_blur_demo(Mat& image) {
Mat dst;
GaussianBlur(image, dst, Size(5, 5), 15);
imshow("高斯模糊", dst);
}
代码解释:卷积操作使用blur() 函数,GaussianBlur() 是高斯模糊的函数。
28.双边模糊
双边模糊(Bilateral blur)是一种图像处理技术,旨在模糊图像中的细节,同时保留边缘的清晰度,从而防止边缘变得模糊或失真。与传统的高斯模糊不同,双边模糊考虑了像素之间的空间距离和像素值之间的差异。具体来说,它利用了一个像素值相似性函数,这个函数在像素之间的距离较大时减小,从而保留了边缘的锐利度。因此,双边模糊常用于需要在保留细节的同时进行图像平滑处理的应用场景,如图像去噪或者一些图像特效的实现中。
c
void QuickDemo::bifilter_demo(Mat& image) {
Mat dst;
bilateralFilter(image, dst, 0, 100, 10);
imshow("双边模糊", dst);
}
29.人脸实时检测
这节通过加载训练好的模型文件调用摄像头 或者加载视频文件实现人脸的实时检测,在实现该项目时需要用到两个文件,这两个文件观看b站视频OpenCV与C++入门30讲相应的视频获得下载地址,下面是代码演示:
c
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
int main(int argc, char** argv) {;
std::string pf_file_path_path = "C:/code/workspace/model/opencv_face_detector_uint8.pb";
std::string pbtxt_file_path = "C:/code/workspace/model/opencv_face_detector.pbtxt";
cv::dnn::Net net = cv::dnn::readNetFromTensorflow(pf_file_path_path, pbtxt_file_path);
VideoCapture cap(0);
cv::Mat frame;
while (true) {
cap.read(frame);
if (frame.empty()) {
break;
}
cv::Mat blob = cv::dnn::blobFromImage(frame, 1.0, Size(300, 300), cv::Scalar(104, 177, 123), false, false);
net.setInput(blob);
cv::Mat probs = net.forward();
// 1x1xNx7
cv::Mat detectMat(probs.size[2], probs.size[3], CV_32F, probs.ptr<float>());
for (int row = 0; row < detectMat.rows; row++) {
float conf = detectMat.at<float>(row, 2);
if (conf > 0.5) {
float x1 = detectMat.at<float>(row, 3) * frame.cols;
float y1 = detectMat.at<float>(row, 4) * frame.rows;
float x2 = detectMat.at<float>(row, 5) * frame.cols;
float y2 = detectMat.at<float>(row, 6) * frame.rows;
cv::Rect box(x1, y1, x2 - x1, y2 - y1);
cv::rectangle(frame, box, cv::Scalar(0, 0, 255), 2, 8);
}
}
cv::imshow("OpenCV4.6DNN人脸检测演示", frame);
char c = waitKey(1);
if (c == 27) {
break;
}
}
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}