OpenCV 学习笔记(C++)(1.4W字)

一切图像皆Mat

OpenCV中图像对象的创建与复制

Mat基本结构

image-20230402202640563

Mat对象数据组成:头部和数据部分,头部存储图像的属性(大小、宽高、图像类型:浮点数类型、字节类型、16位整型、32位整型、双精度浮点型,通道数量和获取途径),数据部分存储所有像素值(像素点)的集合

  • 使用赋值方法,可以将源Mat对象赋值给另一个Mat对象,但两个对象的内存指针仍指向同一个Data Block数据块,数据的本质没有变
  • 使用copyTo或者clone方法,可以重新创建一个数据库给要拷贝的对象来使用,指向的是不同的Data Block数据库

image-20230402203647866

demo01

.h
#pragma once
#include <opencv2/opencv.hpp>
using namespace cv;
class Quickopencv
{
public:
	void mat_create_demo(Mat& image);
};
.cpp
#include "Quickopencv.h"

void Quickopencv::mat_create_demo(Mat& image)
{
	Mat m1, m2;
	m1 = image.clone();
	image.copyTo(m2);

	//创建空白对象
	Mat m3 = Mat::zeros(Size(8,8),CV_8UC1);
    //Mat m3 = Mat::zeros(Size(8,8),CV_8UC3);
	std::cout << m3 << std::endl;
}

main.cpp
#include "Quickopencv.h"

void Quickopencv::mat_create_demo(Mat& image)
{
	Mat m1, m2;
	m1 = image.clone();
	image.copyTo(m2);

	//创建空白对象
	Mat m3 = Mat::zeros(Size(512,512),CV_8UC3);
	m3 = Scalar(255,0,0);
	std::cout << "width:" << m3.cols << ";height:" << m3.rows \
		<<";channels:" <<m3.channels()<< std::endl;
	//std::cout << m3 << std::endl;
	
	//Mat m4 = m3;
	Mat m4;
	m3.copyTo(m4);
	m4 = Scalar(0,255,255);
	Mat m5 = m3.clone();
	m5 = Scalar(0, 0, 255);
	imshow("图像3", m3);
	imshow("图像4", m4);
	imshow("图像5", m5);
}

//创建空白对象
Mat m3 = Mat::zeros(Size(8,8),CV_8UC1);
std::cout << m3 << std::endl;

创建空白对象初始化方法 ones和zeros,像素等于分辨率乘上尺寸

Mat::zeros()方法可以将每个像素点的像素值初始化为1

CV_8UC1:表示8位(8*8=64个像素点)的unsigned char单通道数据

image-20230402205059185

CV_8UC3:创建3通道,一个像素点有3个像素值(3通道)

image-20230402205757368

//创建空白对象
Mat m3 = Mat::zeros(Size(8,8),CV_8UC3);
std::cout << "width:" << m3.cols << ";height:" << m3.rows \
	<<";channels:" <<m3.channels()<< std::endl;
std::cout << m3 << std::endl;

数据真正宽度为 宽*通道数 ->8 * 3=24

image-20230402210519103

Mat::ones()方法只能将每个像素点的第一个通道的像素值初始化为1,其它仍为0

image-20230402211139262

Mat::Scalar(parm1,parm2,parm3)方法为每个像素点的各通道赋值

//创建空白对象
Mat m3 = Mat::zeros(Size(8,8),CV_8UC3);
m3 = Scalar(127,127,127);
std::cout << "width:" << m3.cols << ";height:" << m3.rows \
	<<";channels:" <<m3.channels()<< std::endl;
std::cout << m3 << std::endl;

image-20230402211656705

//创建空白对象
Mat m3 = Mat::zeros(Size(512,512),CV_8UC3);
m3 = Scalar(127,127,127);
std::cout << "width:" << m3.cols << ";height:" << m3.rows \
	<<";channels:" <<m3.channels()<< std::endl;
std::cout << m3 << std::endl;
imshow("创建图像",m3);

image-20230402212350578

创建纯蓝色图像

Mat m3 = Mat::zeros(Size(512,512),CV_8UC3);
m3 = Scalar(255,0,0);
std::cout << "width:" << m3.cols << ";height:" << m3.rows \
	<<";channels:" <<m3.channels()<< std::endl;
//std::cout << m3 << std::endl;
imshow("图像",m3);

image-20230402212716528

使用赋值方法将m3的颜色变成黄色

Mat m3 = Mat::zeros(Size(512,512),CV_8UC3);
m3 = Scalar(255,0,0);
std::cout << "width:" << m3.cols << ";height:" << m3.rows \
	<<";channels:" <<m3.channels()<< std::endl;
//std::cout << m3 << std::endl;

Mat m4 = m3;
m4 = Scalar(0,255,255);
imshow("图像", m3);

image-20230402213016066

通过copyTo和clone方法,得到不同颜色

Mat m3 = Mat::zeros(Size(512,512),CV_8UC3);//尺寸512*512 像素为8位
m3 = Scalar(255,0,0);
std::cout << "width:" << m3.cols << ";height:" << m3.rows \
	<<";channels:" <<m3.channels()<< std::endl;
//std::cout << m3 << std::endl;

//Mat m4 = m3;
Mat m4;
m3.copyTo(m4);
m4 = Scalar(0,255,255);
Mat m5 = m3.clone();
m5 = Scalar(0, 0, 255);
imshow("图像3", m3);
imshow("图像4", m4);
imshow("图像5", m5);

image-20230402213501031

图像像素的读写操作

彩色图像为三通道,灰度图像为单通道

  • 三通道通过image.at<Vec3b>(row, col)方法一次性获取三通道值

    • Vec3b 8位char
    • Vec32 32int型
    • Vec3f 浮点数
  • Vec3b bgr = image.at<Vec3b>(row, col);

    • Vec3b 定义的变量相当于一个数组
.h
#pragma once
#include <opencv2/opencv.hpp>
using namespace cv;
class QuickDemo
{
public:
	void pixel_visit_demo(Mat &image);
};


.cpp
#include "QuickDemo.h"

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;
//			}
//			else if (dims == 3)//彩色图像
//			{
//				Vec3b bgr = image.at<Vec3b>(row, col);
//				image.at<Vec3b>(row, col)[0] = 255 - bgr[0];
//				image.at<Vec3b>(row, col)[1] = 255 - bgr[1];
//				image.at<Vec3b>(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;
				*current_row++ = 255 - pv;
			}
			if (dims == 3) //彩色图像
			{
				*current_row++ = 255 - *current_row;
				*current_row++ = 255 - *current_row;
				*current_row++ = 255 - *current_row;
			}
		}
	}
	imshow("像素读写演示",image);
}

main.cpp
#include <iostream>
#include <opencv2/opencv.hpp>
#include "QuickDemo.h"
using namespace std;
using namespace cv;
int main()
{
	Mat src = imread("D:/img/dog_two.jpg");
	if (src.empty())
	{
		cout << "could not load image...\n";
		return -1;
	}
	imshow("输入窗口",src);
	QuickDemo qd;
	qd.pixel_visit_demo(src);
	waitKey(0);
	destroyAllWindows();
	return 0;
}

图像像素的算术操作

.h
#pragma once
#include <opencv2/opencv.hpp>
using namespace cv;
class QuickDemo
{
public:
	void operator_demo(Mat &img);

};


.cpp
#include "QuickDemo.h"

void QuickDemo::operator_demo(Mat& img)
{
	/*方法一
	Mat dst;
	//dst = img + Scalar(50, 50, 50);
	//imshow("加法操作",dst);
	//dst = img - Scalar(50, 50, 50);
	//imshow("减法操作", dst);
	Mat m = Mat::zeros(img.size(),img.type());
	m = Scalar(2, 2, 2);
	multiply(img,m,dst);
	imshow("乘法操作", dst);
	*/
	/*方法二
	Mat dst = Mat::zeros(img.size(),img.type());
	Mat m = Mat::zeros(img.size(),img.type());
	m = Scalar(50,50,50);
	int w=img.cols;
	int h=img.rows;
	int dims=img.channels();
	for(int row=0;row<h;row++)
		for(int col=0;col<w;col++)
		{
			Vec3b p1=img.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);
	*/

	//方法三
	Mat dst = Mat::zeros(img.size(), img.type());
	Mat m = Mat::zeros(img.size(), img.type());
	m = Scalar(50, 50, 50);
	//add(img,m,dst);
	subtract(img,m,dst);
	//divide(img,m,dst);
	//multiply(img,m,dst);
	imshow("减法操作",dst);
}
main.cpp
#include <iostream>
#include "QuickDemo.h"
using namespace std;
using namespace cv;
int main()
{
	Mat src = imread("D:/img/girlExample.jpg");
	if (src.empty())
	{
		cout << "load image failed...\n";
		return -1;
	}
	imshow("初始图像",src);
	QuickDemo qd;
	qd.operator_demo(src);
	waitKey(0);
	destroyAllWindows();
	return 0;
}

方法一(通道图+标量)

通过imread默认读取三通道,使用加法 三通道图+标量,提升亮度

dst = img + Scalar(50, 50, 50);
imshow(“加法操作”,dst);

image-20230403204308238

使用加法 三通道图-标量,降低亮度

dst = img - Scalar(50, 50, 50);
imshow(“减法操作”, dst);

image-20230403204904412

使用除法法 三通道图/2,降低2倍亮度

dst = img / Scalar(2, 2, 2);
imshow(“除法操作”, dst);

image-20230403204959445

乘法操作,不能直接乘,必须通过创建图像然后通过multiply()函数得到运算结果

void QuickDemo::operator_demo(Mat& img)
{
	Mat dst;
	//dst = img + Scalar(50, 50, 50);
	//imshow("加法操作",dst);
	//dst = img - Scalar(50, 50, 50);
	//imshow("减法操作", dst);
	Mat m = Mat::zeros(img.size(),img.type());
	m = Scalar(2, 2, 2);
	multiply(img,m,dst);//第一个和第二个参数为源图像,第三个为目标图像
	imshow("乘法操作", dst);
}

image-20230403205411444

方法二(手动操作两张图像具体像素点运算)

以加法为例,将像素点像素值提高50

saturate_cast()函数可将像素值下限设置为0,上限为255(防止溢出)

Mat dst = Mat::zeros(img.size(),img.type());
Mat m = Mat::zeros(img.size(),img.type());
m = Scalar(50,50,50);
int w=img.cols;
int h=img.rows;
int dims=img.channels();
for(int row=0;row<h;row++)
	for(int col=0;col<w;col++)
	{
		Vec3b p1=img.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);

image-20230403212345354

方法三(调用OpenCV提供的API,操作两个图像运算)

	Mat dst = Mat::zeros(img.size(), img.type());
	Mat m = Mat::zeros(img.size(), img.type());
	m = Scalar(50, 50, 50);
	//add(img,m,dst);
	subtract(img,m,dst);
	//divide(img,m,dst);
	//multiply(img,m,dst);
	imshow("减法操作",dst);

image-20230403213049330

滚动条操作演示

.h
#pragma once
#include<opencv2/opencv.hpp>
using namespace cv;
class QuickDemo
{
public:
	void tracking_bar_demo(Mat& image);
};

.cpp
#include "QuickDemo.h"
Mat src,dst, m;
int lightness = 50;
static void on_track(int,void*)
{
	m = Scalar(lightness, lightness, lightness);
	add(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 = 100;
	createTrackbar("Value Bar","亮度调整",&lightness,max_value,on_track);
	//第一个参数为拖动条名称	第二个参数为操作窗口名称	第三个参数为实时操作进度条值 第四个参数为最大值	第五个参数为事件函数
	on_track(50,0);
}

main.cpp
#include <iostream>
#include "QuickDemo.h"
using namespace cv;
using namespace std;
int main()
{
	Mat src = imread("D:/img/girlExample.jpg");
	if (src.empty())
	{
		cout << "load image failed...\n";
		return -1;
	}
	imshow("初始图像", src);
	QuickDemo qd;
	qd.tracking_bar_demo(src);
	waitKey(0);
	destroyAllWindows();
	return 0;
}

image-20230403222233860

滚动条调整-参数传递度(调整亮度与对比度)

.h
#pragma once
#include<opencv2/opencv.hpp>
using namespace cv;
class QuickDemo
{
public:
	void tracking_bar_demo(Mat& image);
};

.cpp
#include "QuickDemo.h"

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());
	m = Scalar(b, b, b);
	addWeighted(image,1.0,m,0,b,dst);
	//subtract(image, m, 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)
{
	//在窗口上创建拖动条
	namedWindow("亮度与对比度调整", WINDOW_AUTOSIZE);
	int max_value = 100;
	int lightness = 50;
	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_contrast(100,&image);
}

main.cpp
#include <iostream>
#include "QuickDemo.h"
using namespace cv;
using namespace std;
int main()
{
	Mat src = imread("D:/img/girlExample.jpg");
	if (src.empty())
	{
		cout << "load image failed...\n";
		return -1;
	}
	imshow("初始图像", src);
	QuickDemo qd;
	qd.tracking_bar_demo(src);
	waitKey(0);
	destroyAllWindows();
	return 0;
}

image-20230403231156199

键盘响应操作

通过waitKey()来实现等待键盘按下

void QuickDemo::key_demos(Mat& image)
{
	Mat dst;
	while (true)
	{
		int c = waitKey(100);//停留100ms,等待按键按下
		if (c == 27)//ESC
		{
			break;
		}
		if (c == 49)//Key #1 数字键1
		{
			std::cout << "you enter key #1" << std::endl;
		}
		if (c == 50)//Key #2 数字键2
		{
			std::cout << "you enter key #2" << std::endl;
		}
		if (c == 51)//Key #3 数字键3
		{
			std::cout << "you enter key #3" << std::endl;
		}
	}
}

image-20230404110344394

实现过程

.h
#pragma once
#include <opencv2/opencv.hpp>
using namespace cv;
class QuickDemo
{
public:
	void key_demos(Mat &image);
};

.cpp
#include "QuickDemo.h"

void QuickDemo::key_demos(Mat& image)
{
	Mat dst=Mat::zeros(image.size(),image.type());//初始化dst图像
	while (true)
	{
		int c = waitKey(100);//停留100ms,等待按键按下//注意对视频操作要特殊处理
		if (c == 27)//ESC
		{
			break;
		}
		if (c == 49)//Key #1 数字键1
		{
			std::cout << "you enter key #1" << std::endl;
			cvtColor(image,dst,COLOR_BGR2GRAY);//将图像变为灰度图像
			//imshow("键盘响应",dst);
		}
		if (c == 50)//Key #2 数字键2
		{
			std::cout << "you enter key #2" << std::endl;
			cvtColor(image, dst, COLOR_BGR2HSV);//将图像变为HSV
			//imshow("键盘响应", dst);
		}
		if (c == 51)//Key #3 数字键3
		{
			std::cout << "you enter key #3" << std::endl;
			dst = Scalar(50, 50, 50);
			add(image, dst, dst);
			//imshow("键盘响应", dst);
		}
		imshow("键盘响应", dst);
	}
}

main.cpp
#include <iostream>
#include "QuickDemo.h"
using namespace std;
using namespace cv;
int main()
{
	Mat src = imread("D:/img/girlExample.jpg");
	if (src.empty())
	{
		cout << "load image failed...\n";
		return -1;
	}
	imshow("初始图像", src);
	QuickDemo qd;
	qd.key_demos(src);
	waitKey(0);
	destroyAllWindows();
	return 0;
}

image-20230404112126369

OpenCV自带颜色表操作

image-20230404112500120

src输入图像可以是彩色图像也可以是灰度图像,dst输出图像由COLORMAP决定,现今OpenCV已支持20多种颜色风格

循环转换颜色风格 我们首先把颜色表做成一个枚举数组

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
};

实现过程,思考:如何用按键实现选择显示

.h
#pragma once
#include <opencv2/opencv.hpp>
using namespace cv;
extern int colormap[];

class QuickDemo
{
public:
	void color_style_demo(Mat& image);
};

.cpp
#include "QuickDemo.h"
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)
{
	Mat dst;
	int index=0;
	while (true)
	{
		int c = waitKey(2000);
		if (c == 27)//退出
		{
			break;
		}
		applyColorMap(image,dst,colormap[index%22]);
		index++;
		imshow("22种颜色风格",dst);
	}
}

main.cpp
#include <iostream>
#include <opencv2/opencv.hpp>
#include "QuickDemo.h"
using namespace std;
using namespace cv;
int main()
{
	Mat src = imread("D:/img/girlExample.jpg");
	if (src.empty())
	{
		cout << "load image failed...\n";
		return -1;
	}
	imshow("初始图像", src);
	QuickDemo qd;
	qd.color_style_demo(src);
	waitKey(0);
	destroyAllWindows();
	return 0;
}

image-20230404184405203

图像像素的逻辑操作

.h
#pragma once
#include<opencv2/opencv.hpp>
using namespace cv;
class QuickDemo
{
public:
	void bitwise_demo(Mat &image);
};

.cpp
#include "QuickDemo.h"

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,100,80),Scalar(255,255,0),-1,LINE_8,0);
	rectangle(m2,Rect(150,150,80,80),Scalar(0,255,255),2,LINE_8,0);
	//Rect函数 前两个参数坐标为x,y  后两个参数w,h rectangle第四个参数小于0为填充图像
	//第四个参数大于0为绘制图像,相当于描边 第五个参数为反锯齿LINE_8 LINE_4和LINE_AA

	imshow("m1",m1);
	imshow("m2",m2);
	Mat dst;
	//bitwise_and(m1,m2,dst);//与操作
	//bitwise_or(m1, m2, dst);//或操作
	//bitwise_not(image,dst);//取反操作
	//Mat dst = ~image;//取反操作
	bitwise_xor(m1,m2,dst);//异或
	imshow("像素位操作",dst);
}

main.cpp
#include <iostream>
#include <opencv2/opencv.hpp>
#include "QuickDemo.h"
using namespace std;
using namespace cv;
int main()
{
	Mat src = imread("D:/img/girlExample.jpg");
	if (src.empty())
	{
		cout << "load image failed...\n";
		return -1;
	}
	imshow("初始图像", src);
	QuickDemo qd;
	qd.bitwise_demo(src);
	waitKey(0);
	destroyAllWindows();
	return 0;
}

image-20230405001602792

通道分离与合并

  • 通道分离与合并函数
    • split(para1,para2);//通道分离函数
      • 第一个参数为源图像
      • 第二个参数为目标图像,通常用vector<Mat>存储
    • merge(para1, para2);
      • 第一个参数为源图像,通常用vector<Mat>存储
      • 第二个参数为目标图像
void QuickDemo::channels_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);
}

单通道显示为灰度图像,合并后才能呈现彩色图像

image-20230406001631445

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);

image-20230406001937903

int from_to[] = {0,2,1,1,2,0};
//第0个通道交换第2个通道,第一个通道不变,第2个通道比第0个通道交换
mixChannels(&image,1,&dst,1,from_to,3);//可以有多个源图像,多个源图像用数组
//1张源图像,1张输出图像,3个通道
imshow("通道混合",dst);

image-20230406002728597

.h
#pragma once
#include "opencv2/opencv.hpp"
using namespace cv;
class QuickDemo
{
public:
	void channels_demo(Mat &image);
};

.cpp
#include "QuickDemo.h"

void QuickDemo::channels_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};
	//第0个通道交换第2个通道,第一个通道不变,第2个通道比第0个通道交换
	mixChannels(&image,1,&dst,1,from_to,3);//可以有多个源图像,多个源图像用数组
	//1张源图像,1张输出图像,3个通道
	imshow("通道混合",dst);
}

main.cpp
#include <opencv2/opencv.hpp>
#include "QuickDemo.h"
#include <iostream>
using namespace std;

int main()
{
	Mat src = imread("D:\\千峰嵌入式\\opencv学习\\images\\flower.png");
	if (src.empty())
	{
		cout << "load image failed.../n";
		return 0;
	}
	imshow("初始图像",src);
	QuickDemo qd;
	qd.channels_demo(src);
	waitKey(0);
	destroyAllWindows();
	return 0;
}

图像色彩空间转换

由于HSV色彩空间对不同颜色的像素区分比RGB色彩空间更明显,所有转换成HSV色彩空间再操作

HSV色彩空间像素值对应颜色

image-20230409171658266

	Mat hsv;
	cvtColor(image,hsv,COLOR_BGR2HSV);
	Mat mask;
	inRange(hsv,Scalar(35,43,46),Scalar(77,255,255),mask);
	//inRange hsv源图 +色彩空间范围 最小值-最大值 +目标生成图	//以绿色为例
	imshow("mask",mask);

所得图像,空白部分为可编辑部分(绿色),黑色为不可编辑部分

image-20230409193545090

	bitwise_not(mask,mask);
	imshow("mask", mask);

图像像素取反,得到人物部分为1(可编程)

image-20230409194641461

copyTo(redback,mask)将mask图像上为1的部分迁移到redback上,为0的部分丢弃掉

image-20230409203425399

.cpp
void QuickDemo::inrange_demo(Mat& image)
{
	Mat hsv;
	cvtColor(image,hsv,COLOR_BGR2HSV);
	Mat mask;
	inRange(hsv,Scalar(35,43,46),Scalar(77,255,255),mask);
	//inRange hsv源图 +色彩空间范围 最小值-最大值 +目标生成图	//以绿色为例
	imshow("mask",mask);//白色为1,黑色为0

	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);
}

图像像素值统计

方差和均值多用于图像分析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n1PkK719-1687457045356)(https://gitee.com/MyStarOrbit/cloudimages/raw/master/https://gitee.com/MyStarOrbit/cloudimages/image-20230409215312020.png)]

image-20230409221014333

	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);//mean为均值	stddev为方差
	mean.at<double>(1, 0);
	mean.at<double>(2, 0);
	mean.at<double>(3, 0);

	stddev.at<double>(1, 0);
	stddev.at<double>(2, 0);
	stddev.at<double>(3, 0);//单独打印通道
	std::cout << "means:" << mean << std::endl;
	std::cout << "stddev:" << stddev << std::endl;

image-20230409223136031

纯色图像有均值,但方差恒为0()

double minv, maxv;
Point minLoc, maxLoc;

Mat redback = Mat::zeros(image.size(),image.type());
	redback = Scalar(40,40,200);
	meanStdDev(redback,mean,stddev);
	imshow("redback",redback);
	split(redback, 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;
	}
	std::cout << "means:" << mean << std::endl;
	std::cout << "stddev:" << stddev << std::endl;

image-20230409224207581

.h
#pragma once
#include <opencv2/opencv.hpp>
using namespace cv;
class QuickDemo
{
public:
	void pixel_static_demo(Mat &image);
};

.cpp
#include"QuickDemo.h"
void QuickDemo::pixel_static_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);//mean为均值	stddev为方差
	mean.at<double>(1, 0);
	mean.at<double>(2, 0);
	mean.at<double>(3, 0);

	stddev.at<double>(1, 0);
	stddev.at<double>(2, 0);
	stddev.at<double>(3, 0);//单独打印通道
	std::cout << "means:" << mean << std::endl;
	std::cout << "stddev:" << stddev << std::endl;

	Mat redback = Mat::zeros(image.size(),image.type());
	redback = Scalar(40,40,200);
	meanStdDev(redback,mean,stddev);
	imshow("redback",redback);
	split(redback, 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;
	}
	std::cout << "means:" << mean << std::endl;
	std::cout << "stddev:" << stddev << std::endl;
}

main.cpp
#include <opencv2/opencv.hpp>
#include "QuickDemo.h"
#include <iostream>
using namespace std;

int main()
{
	Mat src = imread("D:\\千峰嵌入式\\opencv学习\\images\\flower.png");
	if (src.empty())
	{
		cout << "load image failed.../n";
		return 0;
	}
	imshow("初始图像", src);
	QuickDemo qd;
	qd.pixel_static_demo(src);
	waitKey(0);
	destroyAllWindows();
	return 0;
}

图像几何形状绘制

OpenCV线型lineType

有三个值可选:LINE_8(默认)、LINE_4LINE_AA

速度:LINE_8>LINE_AA
美观:LINE_AA>LINE_8

image-20230409230821972

image-20230409230826078

image-20230409230831887

绘制矩形

Rect rect;//定义一个矩形、
rect.x = 100;
rect.y = 100;
rect.width = 200;
rect.height = 200;
rectangle(image,rect,Scalar(0,0,255),2,8,0);
//image为输入图片 rect为矩形定义信息 线宽为2 linetype为8 最后一个参数固定为0
rect.x = 100;
rect.y = 400;
rectangle(image, rect, Scalar(0, 0, 255), -1, 8, 0);
//线宽为负数表示填充操作
imshow("绘制演示", image);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-skwpv6HX-1687457045357)(https://note.youdao.com/yws/res/2818/WEBRESOURCEfa5e4a367fe5a503f192eb0e28908cc6)]

绘制圆

在这里插入图片描述

图片叠加

Rect rect;//定义一个矩形、
rect.x = 100;
rect.y = 100;
rect.width = 200;
rect.height = 200;
Mat bg = Mat::zeros(image.size(),image.type());
//rectangle(bg,rect,Scalar(0,0,255),2,8,0);
//image为输入图片 rect为矩形定义信息 线宽为2 linetype为8 最后一个参数固定为0
//rect.x = 100;
//rect.y = 400;
rectangle(image, rect, Scalar(0, 0, 255), -1, 8, 0);
线宽为负数表示填充操作
circle(bg,Point(350,400),50,Scalar(255,0,0),-1,8,0);
//Point为圆点坐标 50为半径 
Mat dst;
addWeighted(image,0.7,bg,0.3,0,dst);//0.7和0.3调整占比
imshow("绘制演示", dst);

在这里插入图片描述

绘制直线

Mat dst;
addWeighted(image,0.7,bg,0.3,0,dst);
line(dst, Point(100, 100), Point(300, 400), Scalar(0,255,0),2,LINE_AA,0);
imshow("绘制演示", dst);

在这里插入图片描述

绘制椭圆

RotatedRect rrt;
rrt.center = Point(200, 200);
rrt.size = Size(100,200);//设置椭圆弧度
rrt.angle = 0.0;//设置角度
ellipse(bg,rrt,Scalar(0,255,255),2,8);
imshow("绘制演示", bg);

角度为0

image-20230409233518505

角度为90

image-20230409233639210

代码小结

void QuickDemo::drawing_demo(Mat& image)
{
	Rect rect;//定义一个矩形、
	rect.x = 100;
	rect.y = 100;
	rect.width = 200;
	rect.height = 200;
	Mat bg = Mat::zeros(image.size(),image.type());
	//rectangle(bg,rect,Scalar(0,0,255),2,8,0);
	//image为输入图片 rect为矩形定义信息 线宽为2 linetype为8 最后一个参数固定为0
	//rect.x = 100;
	//rect.y = 400;
	rectangle(image, rect, Scalar(0, 0, 255), -1, 8, 0);
	线宽为负数表示填充操作
	circle(bg,Point(350,400),50,Scalar(255,0,0),-1,8,0);
	//Point为圆点坐标 50为半径 
	
	Mat dst;
	addWeighted(image,0.7,bg,0.3,0,dst);
	line(dst, Point(100, 100), Point(300, 400), Scalar(0,255,0),2,LINE_AA,0);
	

	RotatedRect rrt;
	rrt.center = Point(200, 200);
	rrt.size = Size(100,200);//设置椭圆弧度
	rrt.angle = 90.0;//设置角度
	ellipse(bg,rrt,Scalar(0,255,255),2,8);
	imshow("绘制演示", bg);
}

随机数与随机颜色

RNG rng(12345);//openCV产生随机数固定写法

rng.uniform(1, 3); 在[1,3)区间,随机生成一个整数

void QuickDemo::random_drawing(Mat& image)
{
	Mat canvas = Mat::zeros(Size(512,512),CV_8UC3);//canvas画布
	int w = canvas.rows;
	int h = canvas.cols;
	RNG rng(12345);//设置随机数种子
	while (true)
	{
		int c = waitKey(10);
		if (c == 27) {
			break;
		}
		else
		{
			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);
		}
	}
}

image-20230409235620908

多边形填充与绘制

polylines()函数绘制|fillPoly()函数填充

多边形绘制

	Mat canvas = Mat::zeros(Size(512,512),CV_8UC3);//创建一个512*512的画布
	Point p1(100,100);
	Point p2(350, 100);
	Point p3(450, 280);
	Point p4(320, 450);
	Point p5(80, 400);
	std::vector<Point>pts(5);//存放多边形点集
	pts.at(0)=p1;
	pts.at(1) = p2;
	pts.at(2) = p3;
	pts.at(3) = p4;
	pts.at(4) = p5;
	polylines(canvas, pts, true, Scalar(0, 0, 255), 2, 8, 0);//多边形绘制函数
	//参数说明:para1:在哪个图上绘制 2:点集 3:true 4:画笔颜色 5:线宽 6:平滑度 7:相对原点位移
	//polylines的assert必须大于0,所以第5的参数不可以填-1用来填充图形

image-20230412231438278

fillPoly(canvas, pts, Scalar(255, 255, 0), 8, 0);//填充函数

image-20230412231952028

drawContours()函数用来绘制轮廓,也可以绘制填充多边形

	//drawContours()用来绘制轮廓,也可以绘制填充多边形
	std::vector<std::vector<Point>>contours;
	contours.push_back(pts);
	drawContours(canvas,contours,-1,Scalar(255,0,0),2);
	//第三个参数为n(0,1,2,3...)表示绘制contours中的第几个点集,-1表示绘制全部点集
	//第五个参数大于0表示绘制,-1表示填充
	imshow("多边形绘制",canvas);

image-20230412232936519

	//drawContours()用来绘制轮廓,也可以绘制填充多边形
	std::vector<std::vector<Point>>contours;
	contours.push_back(pts);
	drawContours(canvas,contours,-1,Scalar(255,0,0),-1);
	//第三个参数为n(0,1,2,3...)表示绘制contours中的第几个点集,-1表示绘制全部点集
	//第五个参数大于0表示绘制,-1表示填充
	imshow("多边形填充",canvas);

image-20230412233010370

.h
#pragma once
#include <opencv2/opencv.hpp>
using namespace cv;
class QuickDemo
{
public:
	void polyline_drawing_demo();
};


.cpp
#include "QuickDemo.h"

void QuickDemo::polyline_drawing_demo()
{
	Mat canvas = Mat::zeros(Size(512,512),CV_8UC3);//创建一个512*512的画布
	Point p1(100,100);
	Point p2(350, 100);
	Point p3(450, 280);
	Point p4(320, 450);
	Point p5(80, 400);
	std::vector<Point>pts(5);//存放多边形点集
	pts.at(0)=p1;
	pts.at(1) = p2;
	pts.at(2) = p3;
	pts.at(3) = p4;
	pts.at(4) = p5;
	//polylines(canvas, pts, true, Scalar(0, 0, 255), 2, 8, 0);//多边形绘制函数
	//参数说明:para1:在哪个图上绘制 2:点集 3:true 4:画笔颜色 5:线宽 6:平滑度 7:相对原点位移
	//polylines的assert必须大于0,所以第5的参数不可以填-1用来填充图形
	//fillPoly(canvas, pts, Scalar(255, 255, 0), 8, 0);//填充函数

	//drawContours()用来绘制轮廓,也可以绘制填充多边形
	std::vector<std::vector<Point>>contours;
	contours.push_back(pts);
	drawContours(canvas,contours,-1,Scalar(255,0,0),-1);
	//第三个参数为n(0,1,2,3...)表示绘制contours中的第几个点集,-1表示绘制全部点集
	//第五个参数大于0表示绘制,-1表示填充
	imshow("多边形填充",canvas);
}

main.cpp
#include <iostream>
#include "QuickDemo.h"
using namespace std;
int main()
{
	QuickDemo qd;
	qd.polyline_drawing_demo();
	waitKey(0);
	destroyAllWindows();
	return 0;
}

鼠标操作与响应

鼠标回调:鼠标回调设置函数+用户自定义函数

image-20230412233559234

初步实现,鼠标抬起才能显示

#include "QuickDemo.h"
//左键右键滚轮
//以左键为例:有三个动作事件 左键按下 鼠标滑动 左键抬起

//创建全局变量来定义初识状态
Point sp(-1, -1);//起始位置
Point ep(-1, -1);//结束位置
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 << 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);
			rectangle(image, box, Scalar(0, 0, 255), 2, 8, 0);//填充绘制一个矩形
			imshow("鼠标绘制", image);//不能忽略,更新图片显示,否则画的图形无法显示出来
			//为下一次绘制做好准备
			sp.x = -1;
			sp.y = -1;
		}
	}
}
void QuickDemo::mouse_drawing_demo(Mat& image)
{
	namedWindow("鼠标绘制",WINDOW_AUTOSIZE);
	setMouseCallback("鼠标绘制",on_draw,(void*)(&image));//第一个参数鼠标事件所在窗口,第二个为回调函数
	imshow("鼠标绘制",image);
}

image-20230413000515020

动态绘制初步实现

#include "QuickDemo.h"
//左键右键滚轮
//以左键为例:有三个动作事件 左键按下 鼠标滑动 左键抬起

//创建全局变量来定义初识状态
Point sp(-1, -1);//起始位置
Point ep(-1, -1);//结束位置
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 << 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);
			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) {
			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);
				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));//第一个参数鼠标事件所在窗口,第二个为回调函数
	imshow("鼠标绘制",image);
}

image-20230413000912136

动态显示完成:通过备份原图来消除上一次轨迹

#include "QuickDemo.h"
//左键右键滚轮
//以左键为例:有三个动作事件 左键按下 鼠标滑动 左键抬起

//创建全局变量来定义初识状态
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 << 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);
			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) {
			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);//将鼠标拖动过程中最新的绘制结果更新给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));//第一个参数鼠标事件所在窗口,第二个为回调函数
	imshow("鼠标绘制",image);
	temp = image.clone();
}

image-20230413001431021

提取ROI区域

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);
            imshow("ROI区域",image(box));
			rectangle(image, box, Scalar(0, 0, 255), 2, 8, 0);//填充绘制一个矩形
			imshow("鼠标绘制", image);//不能忽略,更新图片显示,否则画的图形无法显示出来
			//imshow("ROI区域",image(box));//改变顺序,达到不同显示效果
			//为下一次绘制做好准备
			sp.x = -1;
			sp.y = -1;
		}
	}

image-20230413002352496

image-20230413001911436

再次调用temp.copyTo(image),取消ROI的红色矩形外框


	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);//将鼠标拖动过程中最新的绘制结果更新给image,从而取消ROI的红色矩形外框
			imshow("ROI区域", image(box));
			rectangle(image, box, Scalar(0, 0, 255), 2, 8, 0);//填充绘制一个矩形
			imshow("鼠标绘制", image);//不能忽略,更新图片显示,否则画的图形无法显示出来
			//为下一次绘制做好准备
			sp.x = -1;
			sp.y = -1;
		}
	}

image-20230413112921995

进一步问题优化,将拖动和抬起的判断条件进一步设置范围:ep.x < image.cols && ep.y < image.rows

问题如下,当鼠标超过图片范围,出现bug报错

image-20230413113759918

//解决
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) && (ep.x < image.cols && ep.y < image.rows)) {
			Rect box(sp.x, sp.y, dx, dy);
			temp.copyTo(image);//将鼠标拖动过程中最新的绘制结果更新给image,从而取消ROI的红色矩形外框
			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) {
			ep.x = x;
			ep.y = y;
			int dx = ep.x - sp.x;
			int dy = ep.y - sp.y;
			if ((dx > 0 && dy > 0) && (ep.x < image.cols && ep.y < image.rows)) {
				Rect box(sp.x, sp.y, dx, dy);
				temp.copyTo(image);//将鼠标拖动过程中最新的绘制结果更新给image
				rectangle(image, box, Scalar(0, 0, 255), 2, 8, 0);//填充绘制一个矩形
				imshow("鼠标绘制", image);//不能忽略,更新图片显示,否则画的图形无法显示出来
			}
		}
	}

image-20230413114159221

本节代码

.cpp
#include "QuickDemo.h"
//左键右键滚轮
//以左键为例:有三个动作事件 左键按下 鼠标滑动 左键抬起

//创建全局变量来定义初识状态
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 << 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) && (ep.x < image.cols && ep.y < image.rows)) {
			Rect box(sp.x, sp.y, dx, dy);
			temp.copyTo(image);//将鼠标拖动过程中最新的绘制结果更新给image,从而取消ROI的红色矩形外框
			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) {
			ep.x = x;
			ep.y = y;
			int dx = ep.x - sp.x;
			int dy = ep.y - sp.y;
			if ((dx > 0 && dy > 0) && (ep.x < image.cols && ep.y < image.rows)) {
				Rect box(sp.x, sp.y, dx, dy);
				temp.copyTo(image);//将鼠标拖动过程中最新的绘制结果更新给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));//第一个参数鼠标事件所在窗口,第二个为回调函数
	imshow("鼠标绘制",image);
	temp = image.clone();
}

拓展作业:1、用同样方法绘制一个圆 2、目标检测标注工具

图像像素类型转换与归一化_显示浮点型图像

像素类型转换(常见三种)

像素类型转换

void QuickDemo::norm_demo(Mat& image)
{
	Mat dst;
	std::cout << image.type() << std::endl;//打印原来的数据类型
	image.convertTo(dst,CV_32F);//将dst变为float类型数据
	std::cout << dst.type() << std::endl;//打印转换后的数据类型
    image.convertTo(dst,CV_32F);//将dst变为int类型数据
	std::cout << dst.type() << std::endl;//打印转换后的数据类型
}

image-20230413141520439

  • 16代表CV_8UC3的类型数据,表示每个通道八位的无符号字节类型的RGB三通道彩色图像
  • 21代表CV_32FC3的类型数据,表示RGB三通道的每个通道是32位的浮点数类型图像
  • 20代表CV_32SC3的类型数据,表示RGB三通道的每个通道是32位有符号整型类型图像

像素归一化

image-20230413114536407

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wkPOlZd5-1687457045363)(…/…/typora-user-images/image-20230413114455524.png)]

  • mask是一个二值图像,不为0的像素参与计算,为0的像素直接忽略,表示只对mask区域归一化

四种归一化方式,使数据在0-1之间

image-20230413114628847

对像素进行归一化处理前先转换成浮点数,不能直接对字节型数据进行归一化处理

void QuickDemo::norm_demo(Mat& image)
{
	Mat dst;
	std::cout << image.type() << std::endl;//打印原来的数据类型
	image.convertTo(image,CV_32F);//将dst变为float类型数据
	std::cout << image.type() << std::endl;//打印转换后的数据类型
	//CV_8UC3->CV_32FC3
	//image.convertTo(dst, CV_32S);//将dst变为int类型数据
	//std::cout << dst.type() << std::endl;//打印转换后的数据类型
	normalize(image, dst, 1.0, 0, NORM_MINMAX);//将图像像素归一化在0-1之间
	std::cout << dst.type() << std::endl;
	imshow("图像数据归一化", dst);
}

image-20230413144225698

image-20230413143116399

如果想要imshow对浮点数图像正确显示,浮点数的取值范围必须归一化在0-1之间,即不归一化处理则会出现如下情况(将归一化函数注释掉)

	Mat dst;
	std::cout << image.type() << std::endl;//打印原来的数据类型
	image.convertTo(image,CV_32F);//将dst变为float类型数据
	std::cout << image.type() << std::endl;//打印转换后的数据类型
	//normalize(image, dst, 1.0, 0, NORM_MINMAX);//将图像像素归一化在0-1之间
	std::cout << dst.type() << std::endl;
	imshow("图像数据归一化", image);

image-20230413143905296

思考:如何将归一化后的浮点数转回字节型,只需要将归一化后的浮点型*255,然后再通过convertTo函数转成CV_8U或者CV_32S

	Mat dst;
	std::cout << image.type() << std::endl;//打印原来的数据类型
	image.convertTo(image,CV_32F);//将dst变为float类型数据
	std::cout << image.type() << std::endl;//打印转换后的数据类型
	//CV_8UC3->CV_32FC3
	//image.convertTo(dst, CV_32S);//将dst变为int类型数据
	//std::cout << dst.type() << std::endl;//打印转换后的数据类型
	normalize(image, dst, 1.0, 0, NORM_MINMAX);//将图像像素归一化在0-1之间
	std::cout << dst.type() << std::endl;
	//imshow("图像数据归一化", dst);
	//转回字节型
	dst = dst * 255;
	dst.convertTo(dst, CV_8U);
	std::cout << dst.type() << std::endl;
	imshow("图像数据归一化再转回字节型", dst);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZAixcBGP-1687457045365)(https://gitee.com/MyStarOrbit/cloudimages/raw/master/https://gitee.com/MyStarOrbit/cloudimages/image-20230413145014464.png)]

转回成功,且最后一次打印为CV_8U类型:16

本节代码:

.h
#pragma once
#include<opencv2/opencv.hpp>
using namespace cv;
class QuickDemo
{
public:
	void norm_demo(Mat& image);
};

.cpp
 #include "QuickDemo.h"

void QuickDemo::norm_demo(Mat& image)
{
	Mat dst;
	std::cout << image.type() << std::endl;//打印原来的数据类型
	image.convertTo(image,CV_32F);//将dst变为float类型数据
	std::cout << image.type() << std::endl;//打印转换后的数据类型
	//CV_8UC3->CV_32FC3
	//image.convertTo(dst, CV_32S);//将dst变为int类型数据
	//std::cout << dst.type() << std::endl;//打印转换后的数据类型
	normalize(image, dst, 1.0, 0, NORM_MINMAX);//将图像像素归一化在0-1之间
	std::cout << dst.type() << std::endl;
	//imshow("图像数据归一化", dst);
	//转回字节型
	dst = dst * 255;
	dst.convertTo(dst, CV_8U);
	std::cout << dst.type() << std::endl;
	imshow("图像数据归一化再转回字节型", dst);
}

main.cpp
#include <iostream>
#include "QuickDemo.h"
using namespace std;
int main()
{
	Mat src = imread("D:/img/girlExample.jpg");
	QuickDemo qd;
	qd.norm_demo(src);
	waitKey(0);
	destroyAllWindows();
	return 0;
}

图像放缩与插值

image-20230413145648770

图像放缩

	Mat zoomin, zoomout;//放大(景物拉近)、缩小(景物拉远)
	int h = image.rows;
	int w = image.cols;
	resize(image,zoomout,Size(w/2,h/2),0,0,INTER_LINEAR);
	//fx和fy默认写0,INTER_LINEAR为线性差值(常用)
	imshow("zoomout",zoomout);
	resize(image, zoomin, Size(w * 1.5, h * 1.5), 0, 0, INTER_LINEAR);
	imshow("zoomin", zoomin);

image-20230413150904703

图像插值

四种插值方法见CSDN

思考:通过鼠标事件缩放图片

本节代码

.h
#pragma once
#include <opencv2/opencv.hpp>
using namespace cv;
class QuickDemo
{
public:
	void resize_demo(Mat &image);
};
    
.cpp
void QuickDemo::resize_demo(Mat& image)
{
	Mat zoomin, zoomout;//放大(景物拉近)、缩小(景物拉远)
	int h = image.rows;
	int w = image.cols;
	resize(image,zoomout,Size(w/2,h/2),0,0,INTER_LINEAR);
	//fx和fy默认写0,INTER_LINEAR为线性差值(常用)
	imshow("zoomout",zoomout);
	resize(image, zoomin, Size(w * 1.5, h * 1.5), 0, 0, INTER_LINEAR);
	imshow("zoomin", zoomin);
}

main.cpp
#include <iostream>
#include "QuickDemo.h"
using namespace std;
int main()
{
	Mat src = imread("D:/img/girlExample.jpg");
	QuickDemo qd;
	qd.resize_demo(src);
	waitKey(0);
	destroyAllWindows();
	return 0;
}

图像翻转

filp函数 第一个参数为输入图像 第二个参数为输出图像 第三个参数有三种取值-1、0、1

Mat dst;
flip(image,dst,0);
//上下翻转
imshow("图像翻转", dst);

image-20230413155300886

Mat dst;
flip(image, dst, 1);
//镜像左右翻转
imshow("图像翻转", dst);

image-20230413155328998

Mat dst;
flip(image, dst, -1);
//对角线180°翻转
imshow("图像翻转", dst);

image-20230413155359301

本节代码

.h
#pragma once
#include <opencv2/opencv.hpp>
using namespace cv;
class QuickDemo
{
public:
	void flip_demo(Mat &image);

};
    
.cpp
#include "QuickDemo.h"

void QuickDemo::flip_demo(Mat& image)
{
	Mat dst;
	//flip函数第三个参数有三种取值-1、0、1
	//flip(image,dst,0);
	//上下翻转
	//flip(image, dst, 1);
	//镜像左右翻转
	flip(image, dst, -1);
	//对角线180°翻转
	
	imshow("图像翻转", dst);
}

main.cpp
#include <iostream>
#include "QuickDemo.h"
using namespace std;
int main()
{
	Mat src = imread("D:/img/girlExample.jpg");
	if (src.empty())
	{
		cout << "load image failed...\n";
		return -1;
	}
	imshow("源图", src);
	QuickDemo qd;
	qd.flip_demo(src);
	waitKey(0);
	destroyAllWindows();
	return 0;
}

图像旋转

仿射变换函数

image-20230413160244908

  • 第三个参数M为2 * 3的矩阵
  • 第四个参数为生成的目标图像大小
  • 第五个参数为插值的方式,默认为线性插值
  • 第六个参数为图像边缘形式
  • 第七个参数为图像边缘的颜色,默认为黑色Scalar()

初步旋转实现

Mat dst, M;
int w = image.cols;
int h = image.rows;
M = getRotationMatrix2D(Point2f(w/2,h/2),45,1.0);
//生成2*3矩阵
//第一个参数为原来图像的中心位置Point2f(w/2,h/2)
//第二个参数为旋转角度
//第三个参数为设置放缩,由于warpAffine函数也可以设置缩放,所以这里不需要缩放,设置为1.0
warpAffine(image, dst, M, image.size());//使用默认方法
//warpAffine(image, dst, M, image.size(),INTER_LINEAR,0,Scalar(255,0,0));
imshow("旋转演示", dst);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ccuC6cO4-1687457045366)(https://gitee.com/MyStarOrbit/cloudimages/raw/master/https://gitee.com/MyStarOrbit/cloudimages/image-20230413161853396.png)]

warpAffine(image, dst, M, image.size(),INTER_LINEAR,0,Scalar(255,0,0));//使用边缘处理

image-20230413162100594

发现旋转后的图像并不能完全显现出来

优化实现

求旋转得到的新图宽高

image-20230413162935457

image-20230413163054681

进行矩阵和宽高更新

cos θ和sin θ计算

//由于旋转的度数是任意的,所以一定要保证cos和sin是正数
//abs()得到绝对值
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,0,0));
imshow("旋转演示", dst);

image-20230413165110486

本节代码

.h
#pragma once
#include "opencv2/opencv.hpp"
using namespace cv;
class QuickDemo
{
public:
	void rotate_demo(Mat& image);
};   
    
.cpp
#include "QuickDemo.h"

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);
	//生成2*3矩阵
	//第一个参数为原来图像的中心位置Point2f(w/2,h/2)
	//第二个参数为旋转角度
	//第三个参数为设置放缩,由于warpAffine函数也可以设置缩放,所以这里不需要缩放,设置为1.0
	//warpAffine(image, dst, M, image.size());
	//由于旋转的度数是任意的,所以一定要保证cos和sin是正数
	//abs()得到绝对值
	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,0,0));
	imshow("旋转演示", dst);
}

main.cpp
#include <iostream>
#include "QuickDemo.h"
using namespace std;
int main()
{
	Mat src = imread("D:/img/girlExample.jpg");
	if (src.empty())
	{
		cout << "load image failed...\n";
		return -1;
	}
	imshow("源图", src);
	QuickDemo qd;
	qd.rotate_demo(src);
	waitKey(0);
	destroyAllWindows();
	return 0;
}

视频文件摄像头使用

摄像头使用

void QuickDemo::video_demo(Mat& image)
{
	VideoCapture capture(0);//参数为0:捕获摄像头
	Mat frame;
	while (true)
	{
		capture.read(frame);
		flip(frame,frame,1);//镜像
		if (frame.empty())//用于读取视频文件读到末尾
		{
			break;
		}
		imshow("frame", frame);
		//TODO:do something...(各种图像处理)
		int c = waitKey(10);
		if (c == 27)
		{
			break;
		}
	}
    //release
    capture.release();
}

image-20230413213505028

捕获视频

VideoCapture capture("D:/千峰嵌入式/opencv学习/images/01.mp4");
Mat frame;
while (true)
{
	capture.read(frame);
	flip(frame,frame,1);//镜像
	if (frame.empty())//用于读取视频文件读到末尾
	{
		break;
	}
	imshow("frame", frame);
	//TODO:do something...(各种图像处理)
	int c = waitKey(50);//waitKey中的值决定了视频播放速度,1s多少帧
	if (c == 27)
	{
		break;
	}
    //release
	capture.release();
}

image-20230413214456548

特别注意,如果没有特殊要求,waitKey()参数为1,保证程序实时性

添加事件处理

#include "QuickDemo.h"

void QuickDemo::colorSpace_Demo(Mat& image)
{
	Mat gray, hsv;
	cvtColor(image, hsv, COLOR_BGR2HSV);
	//H 0~180,S,V
	cvtColor(image, gray, COLOR_BGR2GRAY);
	imshow("HSV", hsv);
	imshow("灰度",gray);
}
void QuickDemo::video_demo(Mat& image)
{
	//VideoCapture capture(0);//捕获摄像头
	VideoCapture capture("D:/千峰嵌入式/opencv学习/images/01.mp4");
	Mat frame;
	while (true)
	{
		capture.read(frame);
		flip(frame,frame,1);//镜像
		if (frame.empty())//用于读取视频文件读到末尾
		{
			break;
		}
		imshow("frame", frame);
		//TODO:do something...(各种图像处理)
		colorSpace_Demo(frame);//调用色彩空间转换
		int c = waitKey(50);
		if (c == 27)
		{
			break;
		}
	}
	//release
	capture.release();
}

image-20230413215240037

本节代码:

.h
#pragma once
#include "opencv2/opencv.hpp"
using namespace cv;
class QuickDemo
{
public:
	void colorSpace_Demo(Mat& image);
	void video_demo(Mat& image);
};

.cpp
#include "QuickDemo.h"

void QuickDemo::colorSpace_Demo(Mat& image)
{
	Mat gray, hsv;
	cvtColor(image, hsv, COLOR_BGR2HSV);
	//H 0~180,S,V
	cvtColor(image, gray, COLOR_BGR2GRAY);
	imshow("HSV", hsv);
	imshow("灰度",gray);
}
void QuickDemo::video_demo(Mat& image)
{
	//VideoCapture capture(0);//捕获摄像头
	VideoCapture capture("D:/千峰嵌入式/opencv学习/images/01.mp4");
	Mat frame;
	while (true)
	{
		capture.read(frame);
		flip(frame,frame,1);//镜像
		if (frame.empty())//用于读取视频文件读到末尾
		{
			break;
		}
		imshow("frame", frame);
		//TODO:do something...(各种图像处理)
		colorSpace_Demo(frame);//调用色彩空间转换
		int c = waitKey(50);
		if (c == 27)
		{
			break;
		}
	}
	//release
	capture.release();
}

main.cpp
#include <iostream>
#include "QuickDemo.h"
using namespace std;
int main()
{
	Mat src = imread("D:/img/girlExample.jpg");
	if (src.empty())
	{
		cout << "load image failed...\n";
		return -1;
	}
	//imshow("输入图像", src);
	QuickDemo qd;
	qd.video_demo(src);
	waitKey(0);
	destroyAllWindows();
	return 0;
}

视频处理与保存

视频有分辨率(清新度)如SD、HD、UHD、帧数、帧率和编码等属性

void QuickDemo::video_demo(Mat& image)
{
	//VideoCapture capture(0);//捕获摄像头
	VideoCapture capture("D:/千峰嵌入式/opencv学习/images/01.mp4");
	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 << "frame width:" << frame_width << std::endl;
	std::cout << "frame height:" << frame_height << std::endl;
	std::cout << "FPS:" << fps << std::endl;
	std::cout << "Number of Frames:" << count << std::endl;

	Mat frame;
	while (true)
	{
		capture.read(frame);
		flip(frame, frame, 1);//镜像
		if (frame.empty())//用于读取视频文件读到末尾
		{
			break;
		}
		imshow("frame", frame);
		//TODO:do something...(各种图像处理)
		colorSpace_Demo(frame);//调用色彩空间转换
		int c = waitKey(50);
		if (c == 27)
		{
			break;
		}
	}
	//release
	capture.release();
}

image-20230413221044315

视频保存

void QuickDemo::video_demo(Mat& image)
{
	//VideoCapture capture(0);//捕获摄像头
	VideoCapture capture("D:/千峰嵌入式/opencv学习/images/01.mp4");
	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 << "frame width:" << frame_width << std::endl;
	std::cout << "frame height:" << frame_height << std::endl;
	std::cout << "FPS:" << fps << std::endl;
	std::cout << "Number of Frames:" << count << std::endl;
	VideoWriter writer("D:/test.mp4",capture.get(CAP_PROP_FOURCC),fps,Size(frame_width,frame_height),true);
	//capture.get(CAP_PROP_FOURCC)得到视频编码

	Mat frame;
	while (true)
	{
		capture.read(frame);
		flip(frame, frame, 1);//镜像
		if (frame.empty())//用于读取视频文件读到末尾
		{
			break;
		}
		imshow("frame", frame);
		//TODO:do something...(各种图像处理)
		colorSpace_Demo(frame);//调用色彩空间转换
		writer.write(frame);
		int c = waitKey(50);
		if (c == 27)
		{
			break;
		}
	}
	//release
	capture.release();
	writer.release();
}

image-20230413221919296

如果想自定义视频格式,可以使用set方法

本节代码

.h
#pragma once
#include "opencv2/opencv.hpp"
using namespace cv;
class QuickDemo
{
public:
	void colorSpace_Demo(Mat& image);
	void video_demo(Mat& image);
};

    
.cpp
#include "QuickDemo.h"

void QuickDemo::colorSpace_Demo(Mat& image)
{
	Mat gray, hsv;
	cvtColor(image, hsv, COLOR_BGR2HSV);
	//H 0~180,S,V
	cvtColor(image, gray, COLOR_BGR2GRAY);
	imshow("HSV", hsv);
	imshow("灰度", gray);
}
void QuickDemo::video_demo(Mat& image)
{
	//VideoCapture capture(0);//捕获摄像头
	VideoCapture capture("D:/千峰嵌入式/opencv学习/images/01.mp4");
	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 << "frame width:" << frame_width << std::endl;
	std::cout << "frame height:" << frame_height << std::endl;
	std::cout << "FPS:" << fps << std::endl;
	std::cout << "Number of Frames:" << count << std::endl;
	VideoWriter writer("D:/test.mp4",capture.get(CAP_PROP_FOURCC),fps,Size(frame_width,frame_height),true);
	//capture.get(CAP_PROP_FOURCC)得到视频编码

	Mat frame;
	while (true)
	{
		capture.read(frame);
		flip(frame, frame, 1);//镜像
		if (frame.empty())//用于读取视频文件读到末尾
		{
			break;
		}
		imshow("frame", frame);
		//TODO:do something...(各种图像处理)
		colorSpace_Demo(frame);//调用色彩空间转换
		writer.write(frame);
		int c = waitKey(50);
		if (c == 27)
		{
			break;
		}
	}
	//release
	capture.release();
	writer.release();
}
    
main.cpp
#include <iostream>
#include "QuickDemo.h"
using namespace std;
int main()
{
	Mat src = imread("D:/img/girlExample.jpg");
	if (src.empty())
	{
		cout << "load image failed...\n";
		return -1;
	}
	//imshow("输入图像", src);
	QuickDemo qd;
	qd.video_demo(src);
	waitKey(0);
	destroyAllWindows();
	return 0;
}

图像直方图

image-20230413222640358

image-20230413230750603

如下图所示,右边为左边的图像直方图,统计了像素点(0-255范围内)在图像中出现的次数

图像直方图只反映了图像的像素值信息,不能反映图像的空间信息

image-20230413230914186

image-20230413222710643

实现过程

#include "QuickDemo.h"
//一维直方图
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 };//256个灰度级别
	float hranges[2] = { 0,255 };//通道像素取值范围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表示一张源图,b_hist表示blue通道的直方图输出,1表示一维

	//显示直方图
	int hist_w = 512;
	int hist_h = 400;
	int bin_w = cvRound((double)hist_w/bins[0]);
	Mat histImage = Mat::zeros(hist_h,hist_w,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,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);
	}
	//显示直方图
	namedWindow("Histogram Demo",WINDOW_AUTOSIZE);
	imshow("Histogram Demo",histImage);
}

image-20230413230129624

二维直方图

image-20230413231110557

BIN为步长

#include "QuickDemo.h"

void QuickDemo::histogram_2d_demo(Mat& image)
{
	//2D直方图
	Mat hsv, hs_hist;
	cvtColor(image,hsv,COLOR_BGR2HSV);
	int hbins = 30, sbins = 32;
	//h通道步长:180/30=6 h通道步长:256/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);
	//mask默认为Mat()
	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("D:/hist_2d.png", hist2d_image);
}

image-20230413233417771

直方图均衡化 PS:看不懂先去看计算机图形学

image-20230413233656144

分布函数

image-20230413233708117

均衡化前

image-20230413234125734

均衡化后

image-20230413234152241

直方图均衡化演示(针对灰度图像)

void QuickDemo::histogram_eq_demo(Mat& image)
{
	Mat gray;
	cvtColor(image,gray,COLOR_BGR2GRAY);
	imshow("灰度图像", gray);
	Mat dst;
	equalizeHist(gray, dst);
	imshow("直方图均衡化演示", dst);
}

image-20230413234714568

彩色图像自行补充

本节代码

.h
#pragma once
#include "opencv2/opencv.hpp"
using namespace cv;
class QuickDemo
{
public:
	void histogram_eq_demo(Mat &image);
};

.cpp
#include "QuickDemo.h"

void QuickDemo::histogram_eq_demo(Mat& image)
{
	Mat gray;
	cvtColor(image,gray,COLOR_BGR2GRAY);
	imshow("灰度图像", gray);
	Mat dst;
	equalizeHist(gray, dst);
	imshow("直方图均衡化演示", dst);
}

main.cpp
#include <iostream>
#include "QuickDemo.h"
using namespace std;
int main()
{
	Mat src = imread("D:/img/girlExample.jpg");
	if (src.empty())
	{
		cout << "load image failed...\n";
		return -1;
	}
	//imshow("输入图像", src);
	QuickDemo qd;
	qd.histogram_eq_demo(src);
	waitKey(0);
	destroyAllWindows();
	return 0;
}

图像卷积操作

卷积介绍

通过卷积操作,高的降低,低的提高,降低图像的对比图,使图像产生模糊效果,所以卷积操作又叫做图像模糊

以3*3卷积核为例

image-20230413234937417

image-20230414110852796

image-20230414110612976

从左到右,从上到下,对九宫格中心做卷积操作,完成均值的输出

API函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gWD9x3A7-1687457045373)(…/…/typora-user-images/image-20230414111113541.png)]

实现过程

Ksize默认为1,均值卷积

Size(15*15)表示15 *15的卷积,数值越大,图像越模糊

Point(-1,-1)表示默认用卷积核的中心位置作为输出点

borderType为边缘处理方式

#include "QuickDemo.h"

void QuickDemo::blur_demo(Mat& image)
{
	Mat dst;
	blur(image, dst, Size(15, 15),Point(-1,-1));//二维卷积//边缘处理默认用default
	//blur(image, dst, Size(15, 1), Point(-1, -1));//水平方向一维卷积
	//blur(image, dst, Size(1, 15), Point(-1, -1));//垂直方向一维卷积
	imshow("图像模糊",dst);
}

image-20230414111846205

本节代码

.h
#pragma once
#include "opencv2/opencv.hpp"
using namespace cv;
class QuickDemo
{
public:
	void blur_demo(Mat &image);
};

.cpp
#include "QuickDemo.h"

void QuickDemo::blur_demo(Mat& image)
{
	Mat dst;
	blur(image, dst, Size(15, 15),Point(-1,-1));//二维卷积
	//blur(image, dst, Size(15, 1), Point(-1, -1));//水平方向一维卷积
	//blur(image, dst, Size(1, 15), Point(-1, -1));//垂直方向一维卷积
	imshow("图像模糊",dst);
	//边缘处理默认用default
}

main.cpp
#include <iostream>
#include "QuickDemo.h"
using namespace std;
int main()
{
	Mat src = imread("D:/img/girlExample.jpg");
	if (src.empty())
	{
		cout << "load image failed...\n";
		return -1;
	}
	imshow("输入图像", src);
	QuickDemo qd;
	qd.blur_demo(src);
	waitKey(0);
	destroyAllWindows();
	return 0;
}

高斯模糊

image-20230414113123226

高斯矩阵中中心点系数最大,越远离中心点,系数越小

image-20230414113512983

高斯模糊产生的效果有中心化效益,就是中心像素所占的比重最大,从空间位置上来说,它考虑了中心位置对整个卷积输出的贡献,而均值卷积没考虑到这一点,只是取了平均

image-20230414114929009

高斯双边模糊

介绍

image-20230414135428318

image-20230414133711110

  • 图中左上角阶梯状图像,高阶代表灰度颜色(高像素值),低阶代表黑色部分(低像素值)
  • 白色点代表当前要进行高斯模糊的中心点
  • 用普通高斯模糊会导致图像边缘丢失,导致边缘变模糊
  • 因为彩色图像一共有五个维度:x、y、R、G、B
  • 而普通空间卷积核产生的窗口只会对坐标空间x和y维度进行处理,忽略了RGB三个维度
  • 考虑了色彩空间颜色值的差异,要保留边缘差异,所以还需要另一个高斯卷积核来处理色彩
  • 双边卷积核=坐标空间卷积核 * 色彩空间卷积核
  • 从而通过双边滤波得到右上角的图像

API解释

image-20230414135012335

  • d表示窗口,可以通过sigmaColor和sigmaSpace来计算
  • sigmaSpace为空间窗口的sigma,一般取10或15
  • sigmaColor为色彩窗口的sigma,值越大,处理效果越好

实现过程

.h
#pragma once
#include "opencv2/opencv.hpp"
using namespace cv;
class QuickDemo
{
public:
	void bifilter_demo(Mat &image);

};

.cpp
#include "QuickDemo.h"

void QuickDemo::bifilter_demo(Mat& image)
{
	Mat dst;
	bilateralFilter(image,dst,0,100,10);
	imshow("双边模糊", dst);
}

main.cpp
#include <iostream>
#include "QuickDemo.h"
using namespace std;
int main()
{
	Mat src = imread("D:/千峰嵌入式/opencv学习/images/example.png");
	if (src.empty())
	{
		cout << "load image failed...\n";
		return -1;
	}
	imshow("输入图像", src);
	QuickDemo qd;
	qd.bifilter_demo(src);
	waitKey(0);
	destroyAllWindows();
	return 0;
}

image-20230414124131939

案例:实时人脸识别

人脸识别深度学习模型,OpenCV4及以上版本

image-20230414144914957

.h
#pragma once
#include "opencv2/opencv.hpp"
using namespace cv;
class QuickDemo
{
public:
	void face_detection_demo();

};
    
.cpp
#include "QuickDemo.h"
#include <opencv2/dnn.hpp>

using namespace cv;
using namespace std;
void QuickDemo::face_detection_demo()
{
	std::string root_dir = "D:\\software\\opencv\\sources\\samples\\dnn\\face_detector\\";
	dnn::Net net=cv::dnn::readNetFromTensorflow(root_dir+"opencv_face_detector_uint8.pb",root_dir+"opencv_face_detector.pbtxt");
	//读取深度学习网络(深度学习模型+配置文件)
	VideoCapture capture("D:/千峰嵌入式/opencv学习/images/晴天.mp4");
	Mat frame;
	while (true) {
		capture.read(frame);
		//flip(frame, frame, 1);//镜像用于摄像头
		if (frame.empty())//用于读取视频文件读到末尾
		{
			break;
		}
		Mat blob = dnn::blobFromImage(frame, 1.0, Size(300, 300), Scalar(104,177,123),false,false);
		//读数据
		net.setInput(blob);//NCHW
		//准备数据
		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++)
		{
			float confidence = detectionMat.at<float>(i, 2);
			if (confidence > 0.5) {
				int x1 = static_cast<int>(detectionMat.at<float>(i, 3) * frame.cols);
				int y1 = static_cast<int>(detectionMat.at<float>(i, 4) * frame.rows);
				int x2 = static_cast<int>(detectionMat.at<float>(i, 5) * frame.cols);
				int y2 = static_cast<int>(detectionMat.at<float>(i, 6) * frame.rows);
				Rect box(x1, y1, x2 - x1, y2 - y1);
				rectangle(frame, box, Scalar(0, 0, 255), 2, 8, 0);
			}
		}
		imshow("人脸检测演示", frame);

		int c = waitKey(1);
		if (c == 27)
		{
			break;
		}
	}
}

    
main.cpp
#include <iostream>
#include "QuickDemo.h"
using namespace std;
int main()
{
	QuickDemo qd;
	qd.face_detection_demo();
	waitKey(0);
	destroyAllWindows();
	return 0;
}

演示结果

image-20230414144331907

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/33310.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Soft-prompt Tuning for Large Language Models to Evaluate Bias 论文略读】

Soft-prompt Tuning for Large Language Models to Evaluate Bias 论文略读 INFORMATIONAbstract1 Introduction2 Related work3 Methodology3.1 Experimental setup 4 Results5 Discussion & Conclusion总结A Fairness metricsB Hyperparmeter DetailsC DatasetsD Prompt …

【CSS3系列】第八章 · 伸缩盒模型

写在前面 Hello大家好&#xff0c; 我是【麟-小白】&#xff0c;一位软件工程专业的学生&#xff0c;喜好计算机知识。希望大家能够一起学习进步呀&#xff01;本人是一名在读大学生&#xff0c;专业水平有限&#xff0c;如发现错误或不足之处&#xff0c;请多多指正&#xff0…

服务器配置与操作

服务器配置与操作 一、连接远程服务器 推荐用xshell 或者 finalshell 或者 winSCP 或者 FileZilla xshell下载地址&#xff1a;https://xshell.en.softonic.com/ 二、服务器配置 2.1 安装JDK 2.1 方法一&#xff1a;在线安装 yum list java* yum -y install java-1.8.0-ope…

利用jmeter测试java请求

jmeter和loadrunner一样包含了测试脚本开发、测试执行、以及测试结果统计三个部分。只是jmeter没有脚本开发工具&#xff0c;因此测试java请求的脚本选择在eclipse中进行。 首先介绍如何用eclipse编写接口性能测试脚本。 针对"Java请求"类型的测试&#xff0c;需要…

系列五、NotePad++下载安装

一、下载 链接&#xff1a;https://pan.baidu.com/s/1U2f74vfBJIds7W2wJYnBxg?pwdyyds 提取码&#xff1a;yyds 二、安装 2.1、安装NotePad 解压NotePad-x64.zip至指定目录即可&#xff0c;例如 2.2、安装NppFTP 2.2.1、查看NotePad对应的位数&#xff08;32位or64位&a…

文本分析-使用jieba库实现TF-IDF算法提取关键词

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

程序员找工作难!拿到外包公司的 offer 我应该去么?

引言 前一阵子有一个帖子引起了非常广泛的讨论&#xff0c;描述的就是一个公司的外包工作人员&#xff0c;加班的时候因为吃了公司给员工准备的零食,被公司的HR当场批评&#xff01;这个帖子一发出来&#xff0c;让现在测试行业日益新增的外包公司备受关注。那么外包公司和非外…

驱动开发:内核读写内存多级偏移

让我们继续在《内核读写内存浮点数》的基础之上做一个简单的延申&#xff0c;如何实现多级偏移读写&#xff0c;其实很简单&#xff0c;读写函数无需改变&#xff0c;只是在读写之前提前做好计算工作&#xff0c;以此来得到一个内存偏移值&#xff0c;并通过调用内存写入原函数…

【RF-SSA-LSTM】随机森林-麻雀优化算法优化时间序列预测研究(Python代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

JavaScript内存管理和闭包

1 JavaScript内存管理 2 垃圾回收机制算法 3 闭包的概念理解 4 闭包的形成过程 5 闭包的内存泄漏 一个函数只有调用了外部的变量&#xff0c;才算是闭包。函数内和函数外会写成闭包。 深入JS闭包-闭包的访问过程 <!DOCTYPE html> <html lang"en"> &l…

海气相互作用 - 全球水循环过程及其量级

全球水循环过程及其量级 单位&#xff1a;Sv106m3/s&#xff0c;大气/陆地/海洋(103 km3)径流1.3 Sv≈台湾暖流1.1 Sv≈白令海峡0.9-1.1 Sv 从涡度平衡的角度说明为什么大洋强化发生在西边界而非东边界 有且只有在大洋西边界强化&#xff0c;才可以使得摩擦力产生一个正的涡…

pytorch搭建AlexNet网络实现花分类

pytorch搭建AlexNet网络实现花分类 一、AlexNet网络概述分析 二、数据集准备下载划分训练集和测试集 三、代码model.pytrain.pypredict.py 一、AlexNet网络 概述 使用Dropout的方式在网络正向传播过程中随机失活一部分神经元&#xff0c;以减少过拟合 分析 对其中的卷积层、…

Spring Bean的生命周期解读

目录 1. Spring IOC容器 1.1 Spring IOC 容器的设计 1.1.1 BeanFactory 1.1.2 ApplicationContext 1.2 Spring Bean的生命周期 1.2.1 BeanDefinition 1.2.2 InstantiationAwareBeanPostProcessor和BeanPostProcessor 1.2.3 测试生命周期 1. Spring IOC容器 1.1 Spring …

数据库信息速递 DataStax与谷歌合作将向NoSQL AstraDB引入向量搜索技术

开头还是介绍一下群&#xff0c;如果感兴趣polardb ,mongodb ,mysql ,postgresql ,redis 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;在新加的朋友会分到2群&#xff08;共…

2023-06-23:redis中什么是缓存击穿?该如何解决?

2023-06-23&#xff1a;redis中什么是缓存击穿&#xff1f;该如何解决&#xff1f; 答案2023-06-23&#xff1a; 缓存击穿是指一个缓存中的热点数据非常频繁地被大量并发请求访问&#xff0c;当该热点数据失效的瞬间&#xff0c;持续的大并发请求无法通过缓存获取到数据&…

数学建模-数据的处理

MATLAB数学建模方法与实践&#xff08;第3版&#xff09;——读书笔记 数据的准备数据获取数据处理缺失值处理噪音过滤数据集成数据归约数据变换标准化离散化 数据统计基本描述性统计分布描述性统计 数据可视化数据降维主成分分析&#xff08;PCA&#xff09;相关系数降维 数据…

【数据结构】排序

插入排序 把当前遍历到的元素前的元素序列是排好序的,把当前元素放到前边的序列中进行排序。 直接插入排序 不带哨兵 void InsertSort(int A[],int n) { int i,j,temp; for(i1;i<n;i) if(A[i]<A[i-1]) { tempA[i]; for(ji-1;j>0 && A[j]>temp;--j) A[j…

网络安全系统教程+学习路线(自学笔记)

一、什么是网络安全 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 无论网络、Web、移动、桌面、云等哪个领域&#xff0c;都有攻与防两面…

34岁上岸,我终于圆了自己的考研梦

​ 大家好&#xff0c;我是独孤风&#xff0c;一位曾经的港口煤炭工人&#xff0c;目前在某国企任大数据负责人&#xff0c;公众号大数据流动的作者。 ​ 虽然告诉自己要平静&#xff0c;但是当接到EMS录取通知书的那一刻&#xff0c;眼眶还是忍不住有些湿润。今年正好是是东北…

青岛大学_王卓老师【数据结构与算法】Week03_04_线性表的链式表示和实现4_学习笔记

本文是个人笔记&#xff0c;仅用于学习分享&#xff0c;素材来自青岛大学王卓老师的教学视频&#xff0c;如有侵权&#xff0c;请留言作删文处理。 视频链接&#xff1a; 数据结构与算法基础–第3周04–2.5线性表的链式表示和实现4–单链表基本操作2–销毁单链表 &#x1f4…