OPENCV第四章-图像变换
图像变换
图像插值原理
在图像变换后,可能会出现两个像素变换到一个像素内,或者相邻的两个像素出现变换后不相邻的情况

常用方法
最邻近法 (谁离得近就和谁取相同值)
(双)线性插值

图像缩放

其中 dsizefx,fy是一样的,实际使用只用一个就行,二者是有对应关系的

图像反转
本质就是矩阵的转置

OPENCV没有对flipCode进行类型的定义
图像拼接

横向拼接高度相同,纵向拼接长度相同
示例
#include <opencv2\opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main(int argc, char** argv)
{
Mat gray = imread("lena.png", IMREAD_GRAYSCALE);
Mat smallimg, bigimg, bigimg1, bigimg2;
resize(gray, smallimg, Size(15, 15), 0, 0, INTER_AREA);
resize(smallimg, bigimg, Size(30, 30), 0, 0, INTER_NEAREST);
resize(smallimg, bigimg1, Size(30, 30), 0, 0, INTER_LINEAR);
resize(smallimg, bigimg2, Size(30, 30), 0, 0, INTER_CUBIC);//大小变换
Mat img_x, img_y,img_xy;
flip(gray, img_x, 0);
flip(gray, img_y, 1);
flip(gray, img_xy, -1);//翻转
Mat img_hx, img_hxy;//连接
hconcat(gray, gray, img_hx);
vconcat(img_hx, img_hx, img_hxy);
return 0;
}
仿射变换

只要知道原图像和变换后图像一一对应的三个点就能掌握完全的变换关系
仿射变换矩阵M = \begin{bmatrix} A & B \end{bmatrix} = \begin{bmatrix} a_{00}&a_{01}&b_{00}\\a_{10}&a_{11}&b_{10} \end{bmatrix}
仿射变换原理T = A* \begin{bmatrix} x\\ y\end{bmatrix} +B
图像仿射变换的函数
仿射变换函数


默认BORDER_CONSTANT的特定值为0

可通getRotationMatrix2D()得到旋转的仿射矩阵
旋转中心坐标是一个浮点数,可以给出小数
通过三点对应关系求解变换矩阵

示例
#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>
using namespace std;
using namespace cv;
int main()
{
Mat img = imread("lena.png");
if (img.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
Mat rotation0, rotation1, img_warp0, img_warp1;
double angle = 30; //设置图像旋转的角度
Size dst_size(img.rows, img.cols); //设置输出图像的尺寸
Point2f center(img.rows / 2.0, img.cols / 2.0); //设置图像的旋转中心
rotation0 = getRotationMatrix2D(center, angle, 1); //计算放射变换矩阵
warpAffine(img, img_warp0, rotation0, dst_size); //进行仿射变换
imshow("img_warp0", img_warp0);
//根据定义的三个点进行仿射变换
Point2f src_points[3];
Point2f dst_points[3];
src_points[0] = Point2f(0, 0); //原始图像中的三个点
src_points[1] = Point2f(0, (float)(img.cols - 1));
src_points[2] = Point2f((float)(img.rows - 1), (float)(img.cols - 1));
//放射变换后图像中的三个点
dst_points[0] = Point2f((float)(img.rows) 0.11, (float)(img.cols) 0.20);
dst_points[1] = Point2f((float)(img.rows) 0.15, (float)(img.cols) 0.70);
dst_points[2] = Point2f((float)(img.rows) 0.81, (float)(img.cols) 0.85);
rotation1 = getAffineTransform(src_points, dst_points); //根据对应点求取仿射变换矩阵
warpAffine(img, img_warp1, rotation1, dst_size); //进行仿射变换
imshow("img_warp1", img_warp1);
waitKey(0);
return 0;
}
图像的透视变换
原理

透视变换需要用四个点
变换矩阵为一个3×3的矩阵
求取透视变换矩阵

透视变换函数

示例 (当然肯定是可以通过程序直接获得角点的映射关系)
#include <opencv2\opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
Mat img = imread("noobcvqr.png");
if (img.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
Point2f src_points[4];
Point2f dst_points[4];
//通过Image Watch查看的二维码四个角点坐标
src_points[0] = Point2f(94.0, 374.0);
src_points[1] = Point2f(507.0, 380.0);
src_points[2] = Point2f(1.0, 623.0);
src_points[3] = Point2f(627.0, 627.0);
//期望透视变换后二维码四个角点的坐标
dst_points[0] = Point2f(0.0, 0.0);
dst_points[1] = Point2f(627.0, 0.0);
dst_points[2] = Point2f(0.0, 627.0);
dst_points[3] = Point2f(627.0, 627.0);
Mat rotation, img_warp;
rotation = getPerspectiveTransform(src_points, dst_points); //计算透视变换矩阵
warpPerspective(img, img_warp, rotation, img.size()); //透视变换投影
imshow("img", img);
imshow("img_warp", img_warp);
waitKey(0);
return 0;
}
极坐标变换
极坐标变换就是将图像在直角坐标系与极坐标系中互相变换,形式如图3-26所示,它可以将一圆形图像变换成一个矩形图像,常用于处理钟表、圆盘等图像。圆形图案边缘上的文字经过及坐标变换后可以垂直的排列在新图像的边缘,便于对文字的识别和检测。
可以用warpPolar函数实现
1. void cv::warpPolar(InputArray src,
2. OutputArray dst,
3. Size dsize,
4. Point2f center,
5. double maxRadius,
6. int flags
7. )
src:原图像,可以是灰度图像或者彩色图像。
dst:极坐标变换后输出图像,与原图像具有相同的数据类型和通道数。
dsize:目标图像大小。
center:极坐标变换时极坐标的原点坐标。
maxRadius:变换时边界圆的半径,它也决定了逆变换时的比例参数。
flags: 插值方法与极坐标映射方法标志,插值方法在表3-3中给出,极坐标映射方法在表3-7给出,两个方法之间通过“+”或者“|”号进行连接。
'

示例 (dial是一个表盘)
#include <opencv2\opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main()
{
Mat img = imread("dial.png");
if (!img.data)
{
cout << "请检查图像文件名称是否正确" << endl;
return -1;
}
Mat img1, img2;
Point2f center = Point2f(img.cols / 2, img.rows / 2); //极坐标在图像中的原点
//正极坐标变换
warpPolar(img, img1, Size(300, 600), center, center.x,
INTER_LINEAR + WARP_POLAR_LINEAR);
//逆极坐标变换
warpPolar(img1, img2, Size(img.rows, img.cols), center, center.x,
INTER_LINEAR + WARP_POLAR_LINEAR + WARP_INVERSE_MAP);
imshow("原表盘图", img);
imshow("表盘极坐标变换结果", img1);
imshow("逆变换结果", img2);
waitKey(0);
return 0;
}
图像中绘制几何图形
绘制直线

绘制圆形

thickness为线宽,设定为-1的时候绘制实心圆,其他情况也一样
绘制椭圆形

axes 是长半轴和短半轴的尺寸,size类型
绘制矩形

绘制多边形

pts是一个二维数组,数组的每一个元素都是一个多边形的坐标集
绘制文字

字体类型


ROI区域截取
P.S. start包含,end不包含
深拷贝与浅拷贝

浅拷贝 : 从mat1得到mat2 只复制了矩阵头,而没有复制数据
深拷贝 : 从mat1得到mat2,复制了全部的数据
深拷贝函数

这两个函数一个是函数一个是类方法
浅拷贝只需=即可
mat3 = mat2就相当与浅拷贝mat3
浅拷贝任意数据更改则引用该数据的所有浅拷贝全部会更改
示例
#include <opencv2\opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
Mat img = imread("lena.png");
Mat noobcv = imread("noobcv.jpg");
if (img.empty() || noobcv.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
Mat ROI1, ROI2, ROI2_copy, mask, img2, img_copy, img_copy2;
resize(noobcv, mask, Size(200, 200));
img2 = img; //浅拷贝
//深拷贝的两种方式
img.copyTo(img_copy2);
copyTo(img, img_copy, img);
//两种在图中截取ROI区域的方式
Rect rect(206, 206, 200, 200); //定义ROI区域
ROI1 = img(rect); //截图
ROI2 = img(Range(300, 500), Range(300, 500)); //第二种截图方式
img(Range(300, 500), Range(300, 500)).copyTo(ROI2_copy); //深拷贝
mask.copyTo(ROI1); //在图像中加入部分图像
imshow("加入noobcv后图像", img);
imshow("ROI对ROI2的影响", ROI2);
imshow("深拷贝的ROI2_copy", ROI2_copy);
circle(img, Point(300, 300), 20, Scalar(0, 0, 255), -1); //绘制一个圆形
imshow("浅拷贝的img2", img2);
imshow("深拷贝的img_copy", img_copy);
imshow("深拷贝的img_copy2", img_copy2);
imshow("画圆对ROI1的影响", ROI1);
waitKey(0);
return 0;
}
高斯图像金字塔
高斯图像金字塔原理

通过高斯图像金字塔的每一次采样可以找到缩放不变的特征点
下采样函数

拉普拉斯图像金字塔
拉普拉斯图像金字塔原理

拉普拉斯图像金字塔又称为残差图像金字塔,将得到的高斯图像进行上采样,然后把插值后的上采样图像和原图像进行运算得到的插值为第K层的拉普拉斯图像

生成拉普拉斯图像金字塔必须要用高斯图像金字塔
示例(金字塔)
#include <opencv2\opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
Mat img = imread("lena.png");
if (img.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
vector<Mat> Gauss, Lap; //高斯金字塔和拉普拉斯金字塔
int level = 3; //高斯金字塔下采样次数
Gauss.push_back(img); //将原图作为高斯金字塔的第0层
//构建高斯金字塔
for (int i = 0; i < level; i++)
{
Mat gauss;
pyrDown(Gauss[i], gauss); //下采样
Gauss.push_back(gauss);
}
//构建拉普拉斯金字塔
for (int i = Gauss.size() - 1; i > 0; i--)
{
Mat lap, upGauss;
if (i == Gauss.size() - 1) //如果是高斯金字塔中的最上面一层图像
{
Mat down;
pyrDown(Gauss[i], down); //对第零层人为缩小,主动下采样
pyrUp(down, upGauss);//上采样
lap = Gauss[i] - upGauss;
Lap.push_back(lap);
}
pyrUp(Gauss[i], upGauss);
lap = Gauss[i - 1] - upGauss;//缩小后放大的图像与原先图像的插值 运算
Lap.push_back(lap);
}
//查看两个金字塔中的图像
for (int i = 0; i < Gauss.size(); i++)
{
string name = to_string(i);
imshow("G" + name, Gauss[i]);
imshow("L" + name, Lap[i]);
}
waitKey(0);
return 0;
}
图像窗口操作
创建滑动条

示例
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
//为了能在被调函数中使用,所以设置成全局的
int value;
void callBack(int, void*); //滑动条回调函数
Mat img1, img2;
int main()
{
img1 = imread("lena.png");
if (!img1.data)
{
cout << "请确认是否输入正确的图像文件" << endl;
return -1;
}
namedWindow("滑动条改变图像亮度");//显式创建图像
imshow("滑动条改变图像亮度", img1);
value = 100; //滑动条创建时的初值
//创建滑动条
createTrackbar("亮度值百分比", "滑动条改变图像亮度", &value, 600, callBack, 0);
waitKey();
}
static void callBack(int, void*)
{
float a = value / 100.0;
img2 = img1 a;//调节亮度,像素值倍率
imshow("滑动条改变图像亮度", img2);
}
鼠标响应
鼠标事件响应函数

响应的回调函数



示例
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
Mat img, imgPoint; //全局的图像
Point prePoint; //前一时刻鼠标的坐标,用于绘制直线
void mouse(int event, int x, int y, int flags, void*);
int main()
{
img = imread("lena.png");
if (!img.data)
{
cout << "请确认输入图像名称是否正确! " << endl;
return -1;
}
img.copyTo(imgPoint);
imshow("图像窗口 1", img);
imshow("图像窗口 2", imgPoint);
setMouseCallback("图像窗口 1", mouse, 0); //鼠标影响
waitKey(0);
return 0;
}
void mouse(int event, int x, int y, int flags, void*)
{
if (event == EVENT_RBUTTONDOWN) //单击右键
{
cout << "点击鼠标左键才可以绘制轨迹" << endl;
}
if (event == EVENT_LBUTTONDOWN) //单击左键,输出坐标
{
prePoint = Point(x, y);
cout << "轨迹起始坐标" << prePoint << endl;
}
if (event == EVENT_MOUSEMOVE && (flags & EVENT_FLAG_LBUTTON)) //鼠标按住左键移动第 3 章 图像基本操作
{
//通过改变图像像素显示鼠标移动轨迹
imgPoint.at<Vec3b>(y, x) = Vec3b(0, 0, 255);
imgPoint.at<Vec3b>(y, x - 1) = Vec3b(0, 0, 255);
imgPoint.at<Vec3b>(y, x + 1) = Vec3b(0, 0, 255);
imgPoint.at<Vec3b>(y + 1, x) = Vec3b(0, 0, 255);
imgPoint.at<Vec3b>(y + 1, x) = Vec3b(0, 0, 255);
imshow("图像窗口 2", imgPoint);
//通过绘制直线显示鼠标移动轨迹
Point pt(x, y);
line(img, prePoint, pt, Scalar(0, 0, 255), 2, 5, 0);
prePoint = pt;
imshow("图像窗口 1", img);
}
}