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

news2024/12/23 18:18:36

一切图像皆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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【半监督图像分类 2022 CVPR 】Semi-ViT

【半监督图像分类 2022 CVPR 】Semi-ViT 论文题目&#xff1a;Semi-supervised Vision Transformers at Scale 中文题目&#xff1a; 论文链接&#xff1a;https://arxiv.org/abs/2208.05688 论文代码&#xff1a;https://github.com/amazon-science/semi-vit 发表时间&#xf…

数据结构07:查找[C++][平衡二叉排序树AVL]

图源&#xff1a;文心一言 考研笔记整理1w字&#xff0c;小白友好、代码可跑&#xff0c;请小伙伴放心食用~~&#x1f95d;&#x1f95d; 第1版&#xff1a;查资料、写BUG、画导图、画配图~&#x1f9e9;&#x1f9e9; 参考用书&#xff1a;王道考研《2024年 数据结构考研复习…

Linux 超级漂亮的 Shell

先来一张美图 zsh 介绍 1 Linux shell Linux/Unix 提供了很多种 Shell&#xff0c;为毛要这么多 Shell&#xff1f; 难道用来炒着吃么&#xff1f;那我问你&#xff0c;你同类型的衣服怎么有那么多件&#xff1f;花色&#xff0c;质地还不一样。写程序比买衣服复杂多了&#xf…

在vite+vue3中使用less

目录标题 一、首先&#xff0c;安装插件二、在 vite.config.js中配置三、最后在模版里声明css类型 一、首先&#xff0c;安装插件 npm install less npm install less-loader二、在 vite.config.js中配置 css: {preprocessorOptions: {less: {math: "always", // 括…

VS里拉取时候,变成变基中,变成分离分支状态,git 头指针分离于 baf67ff

分离头指针&#xff08;detached HEAD&#xff09; 通常&#xff0c;我们工作在某一个分支上&#xff0c;比如 master 分支。这个时候 master 指针和 HEAD 指针是一起前进的&#xff0c;每做一次提交&#xff0c;这两个指针就会一起向前挪一步。但是在某种情况下&#xff08;例…

docker搭建达梦数据库

搭建达梦数据库 搭建达梦数据库 拉取镜像 # 拉取镜像 docker pull registry.cn-shanghai.aliyuncs.com/techerwang/dbhub:jem_dm8 或去官网下载 https://eco.dameng.com/document/dm/zh-cn/start/dm-install-docker.html我是选择了去官网下载 然后上传到linux服务器 do…

Selenium教程__按钮的点击、文本框的输入和清除操作(3)

from selenium import webdriverdriver webdriver.Chrome() driver.maximize_window() driver.get("https://www.baidu.com")# 定位元素 input_box driver.find_element_by_id("kw") search_btn driver.find_element_by_id("su")# sen_keys&a…

pycharm修改文件大小限制

场景&#xff1a; 方法&#xff1a; 打开pycharm 安装目录下的idea.properties 增加配置项&#xff1a;idea.max.intellisense.filesize99999

电脑word如何转化为pdf格式,其实非常简单!

作为一名在职场工作多年的资深达人&#xff0c;我们都知道工作中常常需要转换各种文件格式&#xff0c;其中最常见的就是Word文档和PDF文档。有时候根据领导、客户或甲方的要求&#xff0c;我们需要进行格式转换&#xff0c;尤其是将Word转换为PDF格式。PDF格式的文件不仅方便阅…

Spring基础知识(四)

目录 1.Spring包含的模块主要有什么 2.Core Container的作用 3.Data Access/Integration模块的作用 4.AOP模块的作用 5.Spring Web模块的作用 6.Test模块的作用 7.如何将一个类声明为Bean 8.Component和Bean的区别 9.能够注入Bean的注解有什么 10.Resource注解 1.Spr…

【Java】Java 中格式化字符串:String.format() 方法

本文仅供学习参考&#xff01; 相关文章地址&#xff1a; https://www.cnblogs.com/Dhouse/p/7776780.html https://www.javatpoint.com/java-string-format https://www.geeksforgeeks.org/java-string-format-method-with-examples/ 前言 虽然System.out.println()适合调试和…

5 STM32复位和时钟控制

STM32的复位功能 系统复位&#xff1a; 系统复位将复位除时钟控制寄存器CSR中的复位标志和备份区域中的寄存器以外的所有寄存器为它们的复位数值。&#xff08;按键复位和软件复位属于系统复位&#xff09; 电源复位&#xff1a; 电源复位将复位除了备份区域外的所有寄存器。…

【Flink】DataStream API使用之转换算子(Transformation)

转换算子&#xff08;Transformation&#xff09; 数据源读入数据之后&#xff0c;就是各种转换算子的操作&#xff0c;将一个或者多个DataSream转换为新的DataSteam&#xff0c;并且Flink可以针对一条流进行转换处理&#xff0c;也可以进行分流或者河流等多流转换操作&#xf…

sql with子语句的理解

在用sqlite时&#xff0c;用到了with子语句&#xff0c;记录下对其的理解 目的&#xff1a;在1个字段中找到真正包含的数据&#xff0c;一般用于like&#xff0c;但该字段可能是复杂的字符串数组。 例子数据&#xff0c;如&#xff1a; 【E2806894200050010B93C473,123456】…

软件分享:Cpu-Z电脑CPU检测工具介绍(附下载)

目录 一、软件介绍 二、功能介绍 三、使用方法 1、通过 CPU-Z 查看 CPU 信息 2、缓存信息 3、主板信息 4、内存信息 5、内存规格信息&#xff08;SPD&#xff09; CPU是计算机中负责读取指令&#xff0c;对指令译码&#xff0c;并执行指令的核心部件。CPU自产生以来&am…

chatgpt赋能python:用Python计算BIM:优点、应用和结论

用Python计算BIM&#xff1a;优点、应用和结论 Building Information Modeling&#xff08;BIM&#xff09;是一种数字化的建筑设计和施工方法&#xff0c;它包括从建筑物的设计、施工到维护和拆除的整个生命周期。BIM在大规模工程建设中越来越受到欢迎&#xff0c;Python编程…

单片机强排热水器方案

单片机强排热水器方案 MCU型号 MM32SPIN05TW、MM32SPIN080C 产品概述 MM32SPIN05TW本产品使用高性能的ArmCortex-M0为内核的32位MCU&#xff0c;最高工作频率可达72MHz&#xff0c;内置高速存储器&#xff0c;丰富的I/O端口和外设连接到外部总线。本产品包含1个12位ADC、1个比…

【群智能算法改进】基于二次插值策略的改进白鲸优化算法 改进后的EBWO[3]算法【Matlab代码#44】

文章目录 【获取资源请见文章第5节&#xff1a;资源获取】1. 原始BWO算法2. 基于二次插值策略的EBWO算法3. 部分代码展示4. 仿真结果展示5. 资源获取 【获取资源请见文章第5节&#xff1a;资源获取】 1. 原始BWO算法 白鲸优化算法 (BWO&#xff0c;beluga whale optimization)…

宝塔SSL续签以及申请报CURL(7)与CURL(28)错误解决方案

说一下具体背景以及遇到的问题&#xff1a; 主要是网站的SSL过期以后&#xff0c;点击宝塔里的续签功能&#xff0c;弹出一个黑框&#xff0c;等超时后报以下错误 其中也尝试过很多方法&#xff0c;都没什么用&#xff0c;当然&#xff0c;如果报错信息不是这类情况&#xff…

3D开发工具HOOPS可构建PLM程序,数据共享更便捷

产品生命周期管理 (PLM) 解决方案实际上都是将制造生产系统粘合在一起的粘合剂&#xff0c;提供从头到尾的数字主线并为最终用户优化流程。 Tech Soft 3D不是构建 PLM 应用程序的专家&#xff0c;但却对构建领先应用程序所基于的组件技术&#xff0c;以及 SDK 如何提供必要的核…