Opencv学习笔记

# Opencv学习笔记

Day1

图像读取与显示

#include<bits/stdc++.h>
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int argc,char** argv)
{
	Mat a = imread("E:/壁纸/5.jpg");
    //创建一个Windows窗口
	namedWindow("输入窗口", WINDOW_FREERATIO);
    //显示图像
	imshow("输入窗口", a);
    //持续10000ms
	waitKey(10000);
    //关闭所有窗口
	destroyAllWindows();
}

图像色彩空间转换

头文件

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

源文件

#include<bits/stdc++.h>
#include<QuickDemo.h>
void QuickDemo::colorSpace_Demo(Mat& image)
{
	Mat gray, hsv;
	cvtColor(image, hsv, COLOR_BGR2HSV);      //转换色彩空间
	cvtColor(image, gray, COLOR_BGR2GRAY);
	imshow("HSV", hsv);
	imshow("灰度", gray);
	imwrite("E:/hsv.png", hsv);
	imwrite("E:/gray.png", gray);
}
#include<bits/stdc++.h>
#include<opencv2/opencv.hpp>
#include<QuickDemo.h>
using namespace std;
using namespace cv;
int main(int argc,char** argv)
{
	Mat a = imread("E:/壁纸/5.jpg");
	namedWindow("输入窗口", WINDOW_FREERATIO);
	imshow("输入窗口", a);
	QuickDemo qd;
	qd.colorSpace_Demo(a);
	waitKey(10000);
	destroyAllWindows();
}

图像对象的创建与赋值

void QuickDemo::mat_creation_demo(Mat& image)
{
	Mat m1, m2;
	m1 = image.clone();    //克隆
	image.copyTo(m2);      //拷贝

	//创建图像
	//创建一个全是0的8*8的8位的ugsined无符号的单通道的图像
	Mat m3 = Mat::zeros(Size(8, 8), CV_8UC1);

	//输出m3图像
	std::cout << m3 << std::endl;

	//创建一个50*50 8位的ugsined无符号的三通道的图像 每三个数表示一个像素
	Mat m4 = Mat::zeros(Size(50, 50), CV_8UC3);

	//输出m4的宽度 高度 通道数
	std::cout << "width:" << m4.cols << "height:" << m4.rows << "channels:" << m4.channels() << std::endl;

	//创建一个全是1的图像  之能用于单通道
	Mat m5 = Mat::ones(Size(8, 8), CV_8UC1);
	std::cout << m5 << std::endl;

	//第一通道全部赋值127
	m5 = 127;
	std::cout << m5 << std::endl;

	//三个通道分别赋值255 0 0
	m4 = Scalar(255, 0, 0);
	std::cout << m4 << std::endl;

	//显示图像
	//imshow("创建图像", m4);

	//改变m6颜色 m4颜色也改变
	//说明m6指向m4
	Mat m6 = m4;
	m6 = Scalar(0, 255, 255);
	imshow("创建图像", m4);

	//克隆会产生一个独立的个体
	//两者没有关系
	Mat m7 = m4.clone();
	m7 = Scalar(0, 255, 255);
	imshow("图像1", m4);
	imshow("图像2", m7);

	//copyTo操作也会产生一个独立的个体
	Mat m8;
	m4.copyTo(m8);  //将m4 copy给m8
	m8 = Scalar(0, 255, 255);
	imshow("图像3", m8);
}

图像像素的读写操作

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)   //灰度图像
			{
				//将当前这个像素点的颜色变为255-pv;
				int pv = image.at<uchar>(row, col);
				image.at<uchar>(row, col) = 255 - pv;
			}
			if (dims == 3)   //彩色图像
			{
				//获取该像素点的三个值rgb
				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];
			}
		}
	}
	imshow("像素读写演示", image);
}

也可以使用指针 更简洁

//遍历每个像素点
	for (int row = 0; row < h; row++)
	{
		uchar* current_row = image.ptr<uchar>(row);
		for (int col = 0; col < w; col++)
		{
			if (dims == 1)   //灰度图像
			{
				*current_row++ = 255 - *current_row;
			}
			if (dims == 3)   //彩色图像
			{
                //不用麻烦的分三通道 指针会出手
				*current_row++ = 255 - *current_row;
				*current_row++ = 255 - *current_row;
				*current_row++ = 255 - *current_row;
			}
		}
	}

Day2

图像像素的算术操作

void QuickDemo::operators_demo(Mat& image)
{
	//加法操作
	Mat dst;
	dst = image + Scalar(50, 50, 50);
	imshow("加法操作", dst);

	//除法操作
	Mat dst;
	dst = image / Scalar(2, 2, 2);
	imshow("除法操作", dst);

	//矩阵乘法要符合A(n,m) B(m,n)的规定 但可以直接乘常数
	Mat dst;
	dst = image * 2;
	imshow("乘法操作", dst);

	//用multiply函数可以做到矩阵点乘
	Mat dst;
	Mat m = Mat::zeros(Size(8 * 8), CV_8UC3);
	m = Scalar(2, 2, 2);
	multiply(image, m, dst);
	imshow("乘法操作", dst);

	Mat dst = Mat::zeros(image.size(), image.type());
	Mat m = Mat::zeros(image.size(), image.type());
	m = Scalar(50, 50, 50);
	//遍历加法操作
	int w = image.cols;
	int h = image.rows;
	int dims = image.channels();

	for (int row = 0; row < h; row++)
	{
		for (int col = 0; col < w; col++)
		{
			Vec3b p1 = image.at<Vec3b>(row, col);
			Vec3b p2 = m.at<Vec3b>(row,col);
			//saturate_cast<uchar>将该值固定在uchar的范围内
			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);

	//add实现加法操作
	add(image, m, dst);
	imshow("加法操作", dst);

	//subtract实现减法操作
	subtract(image, m, dst);
	imshow("减法操作", dst);

	//divide实现除法操作
	divide(image, m, dst);
	imshow("除法操作", dst);
}

滚动条操作演示-调整图像亮度

//b代表lightness
static void on_track(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);
	add(image, m, dst);
	imshow("亮度调整", dst);
}
void QuickDemo::tracking_bar_demo(Mat& image)
{
	namedWindow("亮度调整", WINDOW_AUTOSIZE);
	int max_value = 100;
	int lightness = 50;
	//进度条名称 作用的窗口名称 改变的变量的地址 改变的最大值 回调函数 给回调函数传的void指针
	createTrackbar("Value Bar:", "亮度调整", &lightness, max_value, on_track, (void*)(&image));
}

融合两个图像

//将两张相同大小,相同类型的图片融合
void QuickDemo::fuse_demo(Mat& image1, Mat& image2)
{
	Mat dst = Mat::zeros(image1.size(), image1.type());
	//第一个矩阵 权重 第二个矩阵 权重 相加后再加上的值 最后存到的变量
	addWeighted(image1, 0.5, image2, 0.5, 0, dst);
	namedWindow("图片合成", WINDOW_AUTOSIZE);
	imshow("图片合成", dst);
}

键盘响应操作

waitKey函数的功能是不断刷新图像,频率时间为delay,单位为ms
返回值为当前键盘按键值。

void QuickDemo::key_demo(Mat& image)
{
	Mat dst = Mat::zeros(image.size(), image.type());
	Mat m = Mat::zeros(image.size(), image.type());
	m = Scalar(50, 50, 50);
	image.copyTo(dst);
	while (1)
	{
		int c = waitKey(100);
		if (c == 27)  //按esc退出
			break;
		if (c == 49) { //按1变成灰度图像
			cvtColor(dst, dst, COLOR_BGR2GRAY);
		}
		if (c == 50) { //按2变成hsv图像
			cvtColor(dst, dst, COLOR_BGR2HSV);
		}
		if (c == 51) { //按3变亮
			add(dst, m, dst);
		}
		imshow("键盘响应", dst);
	}

}

OpenCV自带颜色表操作

void QuickDemo::color_style_demo(Mat& image)
{
	//颜色表
	int color_map[] = {
		COLORMAP_AUTUMN,
		COLORMAP_BONE,
		COLORMAP_CIVIDIS,
		COLORMAP_COOL,
		COLORMAP_DEEPGREEN,
		COLORMAP_HOT,
		COLORMAP_HSV,
		COLORMAP_INFERNO,
		COLORMAP_JET,
		COLORMAP_MAGMA,
		COLORMAP_OCEAN,
		COLORMAP_PARULA,
		COLORMAP_PINK,
		COLORMAP_PLASMA,
		COLORMAP_RAINBOW,
		COLORMAP_SPRING,
		COLORMAP_SUMMER,
		COLORMAP_TURBO,
		COLORMAP_TWILIGHT,
		COLORMAP_TWILIGHT_SHIFTED,
		COLORMAP_VIRIDIS,
		COLORMAP_WINTER
	};

	Mat dst;
	int index = 0;
	while (1)
	{
		int c = waitKey(2000);
		if (c == 27)  //退出
			break;
		//变换颜色
		applyColorMap(image, dst, color_map[index]);
		index++;
		index %= 19;
		imshow("颜色风格", dst);
	}
}

图像像素的逻辑操作

void QuickDemo::bitwise_demo(Mat& image)
{
	Mat m1 = Mat::zeros(Size(256, 256), CV_8UC3);
	Mat m2 = Mat::zeros(Size(256, 256), CV_8UC3);
	//图像 左上角坐标 矩形大小 颜色 小于0表示填充大于0表示绘制 周围8个像素提供支援 坐标点的小数点位数
	rectangle(m1, Rect(100, 100, 80, 80), Scalar(255, 255, 0), -1, LINE_8, 0);
	rectangle(m2, Rect(150, 150, 80, 80), Scalar(0, 255, 255), -1, LINE_8, 0);

	//2表示边的线宽
	rectangle(m1, Rect(100, 100, 80, 80), Scalar(255, 255, 0), 5,LINE_8, 0);
	rectangle(m2, Rect(150, 150, 80, 80), Scalar(0, 255, 255), 5,LINE_8, 0);
	
	imshow("m1", m1);
	imshow("m2", m2);

	Mat dst;
	//和操作
	bitwise_and(m1, m2, dst);
	//或操作
	bitwise_or(m1, m2, dst);
	//取反操作
	bitwise_not(m1, dst);
	//也可以这样取反
	dst = ~m1;
	//异或操作
	bitwise_xor(m1, m2, dst);
	imshow("像素位操作", dst);
}

通道分离与合并

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

	//通道混合
	Mat dst = Mat::zeros(image.size(), image.type());
	//原图像的0通道复制到输出图像的2通道 以此类推
	int from_to[] = { 0,2,1,1,2,0 };
	//原图像的地址 矩阵个数 输出图像的地址 矩阵个数 索引 索引对个数
	mixChannels(&image, 1, &dst, 1, from_to, 3);
	imshow("通道混合", dst);
}

Day3

图像色彩空间转换

void QuickDemo::inrange_demo(Mat& image)
{
	Mat hsv;
	cvtColor(image, hsv, COLOR_BGR2HSV);
	Mat mask;
	//把白色的区域都赋为1其余为0 具体见表
	inRange(hsv, Scalar(0, 0, 221), Scalar(180, 30, 255), mask);
	imshow("mask1", mask);

	Mat redback = Mat::zeros(image.size(), image.type());
	//红色
	redback = Scalar(40, 40, 200);
	//取反
	bitwise_not(mask, mask);
	imshow("mask2", mask);
	//把image赋给redback 但只赋值mask上为1的点
	image.copyTo(redback, mask);
	imshow("redback", redback);
}

图像像素值统计

void QuickDemo::pixel_statisitc_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);
		std::cout << "min value:" << minv << "max value:" << maxv << std::endl;
	}
	Mat mean, stddev;
	//均值 标准
	meanStdDev(image, mean, stddev);
	std::cout << "means:" << mean << "stddev" << stddev << std::endl;
}

图像几何形状绘制

void QuickDemo::drawing_demo(Mat& image)
{
	//定义一个矩形
	Rect rect;
	//左上角坐标
	rect.x = 200;
	rect.y = 200;
	rect.width = 100;
	rect.height = 100;
	//在image上绘制一个矩形
	//rectangle(image, rect, Scalar(0, 0, 255), 5, 8, 0);
	
	//绘制圆 圆心坐标 半径 颜色 线宽 锯齿 小数点
	//circle(image, Point(200, 200), 15, Scalar(255, 0, 0), 1, 8, 0);

	Mat dst, bg = Mat::zeros(image.size(), image.type());
	rectangle(bg, rect, Scalar(0, 0, 255), -1, 8, 0);

	//绘制一条线 左上角坐标 右下角坐标 颜色 线宽 锯齿 小数点
	line(bg, Point(100, 100), Point(350, 400), Scalar(0, 255, 0), 2, 8, 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);
	//将image和bg融合权值分配7:3
	addWeighted(image, 0.7, bg, 0.3, 0, dst);
	imshow("绘制演示", bg);
}

Day4

随机数与随机颜色

void QuickDemo::random_drawing(Mat& image)
{
	Mat canvas = Mat::zeros(Size(1024, 1024), CV_8UC3);
	//RNG类 随机数种子
	RNG rng(12345);
	while (true) {
		int c = waitKey(10);
		if (c == 27)
			break;
		//[0,1024)中的随机值
		int x1 = rng.uniform(0, 1024);
		int y1 = rng.uniform(0, 1024);
		int x2 = rng.uniform(0, 1024);
		int y2 = rng.uniform(0, 1024);
		int r = rng.uniform(0, 255);
		int g = rng.uniform(0, 255);
		int b = rng.uniform(0, 255);
		line(canvas, Point(x1, y1), Point(x2, y2), Scalar(r, g, b), 4, LINE_AA, 0);
		imshow("随即绘制演示", canvas);
	}
}

多边形填充与绘制

void QuickDemo::polyline_drawing_demo()
{
	Mat canvas = Mat::zeros(Size(512, 512), CV_8UC3);
	std::vector<Point>pts;
	pts.push_back({ 100,100 });
	pts.push_back({ 350,100 });
	pts.push_back({ 450,280 });
	pts.push_back({ 320,450 });
	pts.push_back({ 80,400 });

	//填充多边形
	fillPoly(canvas, pts, Scalar(255, 255, 0), 8, 0);
	//绘制一个多边形 顶点坐标集合 是否把绘制的多条线段首尾相连,显示,如果要绘制多边形,则这个参数值该置为true
	polylines(canvas, pts, true, Scalar(0, 0, 255), 1, 8, 0);
	imshow("多边形绘制", canvas);

	std::vector<std::vector<Point> >contours;
	contours.push_back(pts);
	//绘制第几个轮廓 颜色 线宽 -1为填充
	drawContours(canvas, contours, -1, Scalar(255, 0, 0), -1);
	imshow("多边形绘制", canvas);
}

鼠标操作与响应

Point sp(-1, -1);
Point ep(-1, -1);
Mat dst;
//鼠标事件 鼠标坐标 鼠标事件2 传递参数
static void on_draw(int event, int x, int y, int flags, void* userdata)
{
	//把传递的图像传给image
	Mat image = *((Mat*)userdata);
	//左键按下
	if (event == 1)
	{
		sp.x = x;
		sp.y = y;
		std::cout << "start point:" << sp << std::endl;
	}
	//左键升起
	else if (event == 4)
	{
		ep.x = x;
		ep.y = y;
		int dx = ep.x - sp.x;
		int dy = ep.y - sp.y;
		if ((dx > 0) and (dy > 0))
		{
			Rect box(sp.x, sp.y, dx, dy);
			rectangle(image, box, Scalar(0, 0, 255), 2, 8, 0);
			imshow("鼠标绘制", image);
			imshow("截图", image(box));
			sp.x = -1;
			sp.y = -1;
		}
	}
	//鼠标移动
	else if (event == 0)
	{
		if ((sp.x > 0) and (sp.y > 0))
		{
			ep.x = x;
			ep.y = y;
			int dx = ep.x - sp.x;
			int dy = ep.y - sp.y;
			if ((dx > 0) and (dy > 0))
			{
				Rect box(sp.x, sp.y, dx, dy);
				//覆盖 只显示一个矩形
				dst.copyTo(image);
				rectangle(image, box, Scalar(0, 0, 255), 2, 8, 0);
				imshow("鼠标绘制", image);
			}
		}
	}
}
void QuickDemo::mouse_drawing_demo(Mat &image)
{
	namedWindow("鼠标绘制", WINDOW_AUTOSIZE);
	//鼠标指令 窗体 回调函数 传递参数
	setMouseCallback("鼠标绘制", on_draw, (void*) (&image));
	dst = image.clone();
}
event#defineCV_EVENT_MOUSEMOVE 0 移动
#defineCV_EVENT_LBUTTONDOWN 1 左键按下
#defineCV_EVENT_RBUTTONDOWN 2 右键按下
#defineCV_EVENT_MBUTTONDOWN 3 中键按下
#defineCV_EVENT_LBUTTONUP 4 左键升起
#defineCV_EVENT_RBUTTONUP 5 右键升起
#defineCV_EVENT_MBUTTONUP 6 中键升起
#defineCV_EVENT_LBUTTONDBLCLK 7 左键双击
#defineCV_EVENT_RBUTTONDBLCLK 8 右键双击
#defineCV_EVENT_MBUTTONDBLCLK 9 中键双击
#defineCV_EVENT_MOUSEHWHEEL 10 滚轮事件
flag:
#defineCV_EVENT_FLAG_LBUTTON 1 左键拖曳
#defineCV_EVENT_FLAG_RBUTTON 2 右键拖曳
#defineCV_EVENT_FLAG_MBUTTON 4 中键拖曳
#defineCV_EVENT_FLAG_CTRLKEY 8 (8~15)按Ctrl不放事件
#defineCV_EVENT_FLAG_SHIFTKEY 16 (16~31)按Shift不放事件
#defineCV_EVENT_FLAG_ALTKEY 32 (32~39)按Alt不放事件

Day5

图像像素类型转换与归一化

void QuickDemo::norm_demo(Mat& image)
{
	Mat dst;
	std::cout << image.type() << std::endl;
	//转换图像像素类型
	image.convertTo(image, CV_32F);
	std::cout << image.type() << std::endl;
	//归一化 
	//NORM_MINMAX:将数组的数值归一化到[alpha,beta]内,常用。
	//NORM_L1:归一化数组的L1 - 范数(绝对值的和)
	//NORM_L2 : 归一化数组的(欧几里德)L2 - 范数
	normalize(image, dst, 1.0, 0, NORM_MINMAX);
	std::cout << dst.type() << std::endl;
	imshow("图像数据归一化", dst);
}

归一化作用
归一化就是要把需要处理的数据经过处理后(通过某种算法)限制在你需要的一定范围内。首先归一化是为了后面数据处理的方便,其次是保证程序运行时收敛加快

归一化的具体作用是归纳统一样本的统计分布性。归一化在0-1之间是统计的概率分布,归一化在某个区间上是统计的坐标分布。归一化有同一、统一和合一的意思。

此外,归一化还有其他作用:
1.无量纲化
例如房子数量和收入,从业务层知道这两者的重要性一样,所以把它们全部归一化,这是从业务层面上作的处理。

2.避免数值问题
不同的数据在不同列数据的数量级相差过大的话,计算起来大数的变化会掩盖掉小数的变化。

3.一些模型求解的需要
例如梯度下降法,如果不归一化,当学习率较大时,求解过程会呈之字形下降。学习率较小,则会产生直角形路线,不管怎么样,都不会是好路线。

4.时间序列
进行log分析时,会将原本绝对化的时间序列归一化到某个基准时刻,形成相对时间序列,方便排查。

5.收敛速度
加快求解过程中参数的收敛速度。

归一化目的
归一化的目的简而言之,是使得没有可比性的数据变得具有可比性,同时又保持相比较的两个数据之间的相对关系,如大小关系;或是为了作图,原来很难在一张图上作出来,归一化后就可以很方便的给出图上的相对位置等。

图像放缩与插值

void QuickDemo::resize_demo(Mat& image)
{
	Mat zoomin, zoomout;
	int h = image.rows;
	int w = image.cols;
	//缩放图像
	/*src - 输入图像。
	dst - 输出图像;它的大小为 dsize(当它非零时)或从 src.size()、fx 和 fy 计算的大小;dst 的类型与 src 的类型相同。
	dsize - 输出图像大小;如果它等于零,则计算为:dsize = Size(round(fx * src.cols), round(fy * src.rows))。dsize 或 fx 和 fy 必须为非零。
	fx - 沿水平轴的比例因子;当它等于 0 时,它被计算为(double)dsize.width / src.cols
	fy - 沿垂直轴的比例因子;当它等于 0 时,它被计算为(double)dsize.height / src.rows
	interpolation 内插方式 内插方式有
	CV_INTER_NEAREST 最邻近插值点法
	CV_INTER_LINEAR 双线性插值法
	CV_INTER_AREA 邻域像素再取样插补
	CV_INTER_CUBIC 双立方插补,4 * 4大小的补点*/
	resize(image, zoomin, Size(w / 2, h / 2), 0, 0, INTER_LINEAR);

	imshow("zoomin", zoomin);
}

滚轮控制图像放缩

Mat dst;
static void on_draw(int event, int x, int y, int flags,void*userdata)
{
	if (event == 10)
	{
		int op;
		if (flags > 0)
			op = 10;
		else op = -10;
		int h = dst.rows;
		int w = dst.cols;
		resize(dst, dst, Size(w + op, h + op), 0, 0, INTER_LINEAR);
		imshow("图像缩放", dst);
	}
}
void QuickDemo::mouse_resize_demo(Mat& image)
{
	namedWindow("图像缩放", WINDOW_AUTOSIZE);
	setMouseCallback("图像缩放", on_draw, NULL);
	dst = image;
}

Day6

图像翻转

void QuickDemo::flip_demo(Mat& image)
{
	Mat dst;
	//0 上下翻转 -1上下左右翻转 1左右翻转
	flip(image, dst, 1);
	imshow("图像翻转", dst);
}

图像旋转

void QuickDemo::rotate_demo(Mat& image)
{
	Mat dst, M;
	int w = image.cols;
	int h = image.rows;
	//中心点,角度,放缩比
	M = getRotationMatrix2D(Point2f(w / 2, h / 2), 45, 1.0);
	double cos = abs(M.at<double>(0, 0));   //旋转角度的cos
	double sin = abs(M.at<double>(0, 1));   //旋转角度的sin
	int nw = cos * w + sin * h;
	int nh = sin * w + cos * h;
	//中心点偏移
	M.at<double>(0, 2) += (nw / 2 - w / 2);
	M.at<double>(1, 2) += (nh / 2 - h / 2);
	//旋转矩阵 大小 插值方式的组合 边缘像素模式 颜色
	warpAffine(image, dst, M, Size(nw, nh), INTER_LINEAR, 0, Scalar(255, 0, 0));
	imshow("旋转演示", dst);
}

posted @   ShadowAA  阅读(43)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
点击右上角即可分享
微信分享提示