OpenCV常见的图像变换

拉伸、收缩、扭曲和旋转

最简单的图像变换是调整图像大小,使其变大或变小。但实际操作时要比想象的复杂一些,因为调整大小带来了像素如何插值(放大)或合并(减少)的问题。

均匀调整

cv::resize()

void cv::resize(InputArray src, OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=cv::INTER_LINEAR)

作用:根据设置调整图像为指定大小,其中有两种指定方式:① 绝对尺寸:通过dsize直接设置;② 相对尺寸:dsize设置为cv::Size(0,0),然后将fx和fy设置为我们想要的比例因子即可。

插值方法
插值 含义
cv::INTER_LINEAR 双线性插值法
cv::INTER_NEAREST 最近邻插值
cv::INTER_AREA 像素区域重采样
cv::INTER_CUBIC 双三次插值
cv::INTER_LANCZ0S4 插值(超过8×8个邻域)
#include "stdafx.h"
#include <opencv2/opencv.hpp>


int main()
{
	cv::namedWindow("image", cv::WINDOW_NORMAL);
	cv::namedWindow("nearest", cv::WINDOW_NORMAL);
	cv::namedWindow("linear", cv::WINDOW_NORMAL);
	cv::namedWindow("area", cv::WINDOW_NORMAL);
	cv::namedWindow("cubic", cv::WINDOW_NORMAL);
	cv::namedWindow("lancz0s4", cv::WINDOW_NORMAL);
	cv::Mat image = cv::imread("D:\\personal-data\\wallpapers\\cat.jpg");
	cv::Mat nearest, linear, area, cubic, lancz0s4;
	cv::resize(image, nearest, cv::Size(0, 0), 2, 2);
	cv::resize(image, linear, cv::Size(0, 0), 2, 2);
	cv::resize(image, area, cv::Size(0, 0), 2, 2);
	cv::resize(image, cubic, cv::Size(0, 0), 2, 2);
	cv::resize(image, lancz0s4, cv::Size(0, 0), 2, 2);
	cv::imshow("image", image);
	cv::imshow("nearest", nearest);
	cv::imshow("linear", linear);
	cv::imshow("area", area);
	cv::imshow("cubic", cubic);
	cv::imshow("lancz0s4", lancz0s4);
	cv::waitKey(0);
	cv::destroyAllWindows();
    return 0;
}

图像金字塔

关于图像金字塔,在之前的学习过程中已经详细介绍过,详细可以点击链接:OpenCV图像金字塔

其中详细介绍了其原理,以及生成金字塔的过程。在这里将补充一个自动生成高斯金字塔的方法:

void cv::buildPyramid(InputArray src, OutputArrayOfArrays dst, int maxlevel)

不均匀映射

可以拉伸、收缩、扭曲或转换图像的功能称为几何变换。对于平面区域,有两种几何变换:使用2×3矩阵的变换称为“仿射变换”;使用3×3矩阵的变换称为“透视变换”或“同形”。

仿射变换

仿射变换是可以以矩阵乘法后跟向量加法的形式表示的任何变换。在OpenCV中,该变换的标准样式是2×3矩阵。定义如下:

A\equiv \begin{bmatrix} a_{00} &a_{01} \\ a_{10}& a_{11} \end{bmatrix}B\equiv \begin{bmatrix} b_{0}\\ b_{1} \end{bmatrix}T\equiv \begin{bmatrix} A & B \end{bmatrix}X\equiv \begin{bmatrix} x\\ y \end{bmatrix}X'\equiv \begin{bmatrix} x\\ y\\ 1 \end{bmatrix}

仿射变换可以将矩形转换为平行四边形,它们可以挤压形状,但是必须保持对边平行,可以旋转或缩放。

密集仿射变换函数cv::warpAffine()

void cv::warpAffine(InputArray src, OutputArray dst, InputArray M, cv::Size dsize, int flags=cv::INTER_LINEAR, int borderMode=cv::BORDER_CONSTANT, const cv::Scalar& borderValue=cv::Scalar())

作用:对输入矩阵做仿射变换,为了输出图像平滑而自然,需要处理内插。M为前面提到的2×3的矩阵;dsize是目标图像尺寸;flags是插值方法,和resize()方法里的插值方法相同,这里有个附加个cv::WARP_INVERSE_MAP(组合使用),用于指明是否可以反向变换;最后两个参数则是边界外推时所用的参数。

dst(x,y)=src(M_{00}x+M_{01}y+M_{02}, M_{10}x+M_{11}y+M_{12})

计算仿射映射矩阵的cv::getAffineTransform()和cv::getRotationMatrix2D()

cv::Mat cv::getAffineTransform(const cv::Point2f* src, const cv::Point2f* dst)

作用:生成映射矩阵M。src和dst是三个二维(x,y)点的数组。

cv::Mat cv::getRotationMatrix2D(cv::Point2f center, double angle, double scale)

作用:生成映射矩阵M。center是旋转中心,angle是旋转中心,scale是重新调整。

#include "stdafx.h"
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;

int main()
{
	cv::Mat src = cv::imread("D:\\personal-data\\wallpapers\\test.png");
	if (src.empty())
	{
		return -1;
	}

	cv::Point2f srcTri[] = { cv::Point2f(0, 0),cv::Point2f(src.cols - 1, 0),cv::Point2f(0, src.rows - 1) };
	cv::Point2f dstTri[] = { cv::Point2f(src.cols * 0.f, src.rows * 0.33f),cv::Point2f(src.cols * 0.85f, src.rows * 0.25f), cv::Point2f(src.cols * 0.15, src.rows * 0.7f) };
	cv::Mat warp_mat = cv::getAffineTransform(srcTri, dstTri);
	cv::Mat dst, dst2;
	cv::warpAffine(src, dst, warp_mat, src.size(), cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar());
	for (int i = 0; i < 3; ++i)
	{
		cv::circle(dst, dstTri[i], 5, cv::Scalar(255, 0, 255), -1, cv::LINE_AA);
	}
	cv::imshow("affineTransform", dst);
	cv::waitKey(0);

	for (int frame = 0;; ++frame) {
		cv::Point2f center(src.cols * 0.5f, src.rows * 0.5f);
		double angle = frame * 3 % 360;
		double scale = (cos((angle - 60) * CV_PI / 180) + 1.05) * 0.8;
		cv::Mat rot_mat = cv::getRotationMatrix2D(center, angle, scale);
		cv::warpAffine(src, dst, rot_mat, src.size(), cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar());
		cv::imshow("Rotated image", dst);
		if (cv::waitKey(30) >= 0) {
			break;
		}
	}
	cv::destroyAllWindows();
    return 0;
}

cv::transform()用于稀疏仿射变换

void cv::transform(InputArray src, OutputArray dst, InputArray mtx)

作用:生成稀疏矩阵的仿射变换。输入输出都是N×1,但是通道数可能不同,分别为Ds和Dd,这mtx是Ds×Dd的矩阵。

cv::invertAffineTransform()用于逆仿射变换

void cv::invertAffineTransform(InputArray M, OutputArray iM)

透视变换

透视投影完全由单个矩阵指定,但实际上不是线性变换,因为变换需要通过最终维度进行划分,从而在过程中失去一个维度。

cv::warpPerspective()用于密集透视变换

void cv::warpPerspective(InputArray src, OutputArray dst, InputArray M, cv::Size dsize, int flags=cv::INTER_LINEAR, int borderMode=cv::BORDER_CONSTANT, const cv::Scalar& borderValue=cv::Scalar())

作用:对输入矩阵做透视变换,为了输出图像平滑而自然,需要处理内插。M为前面提到的3×3的矩阵;dsize是目标图像尺寸;flags是插值方法,和resize()方法里的插值方法相同,这里有个附加个cv::WARP_INVERSE_MAP(组合使用),用于指明是否可以反向变换;最后两个参数则是边界外推时所用的参数。

dst(x,y)=src(\frac{M_{00}x+M_{01}y+M_{02}}{M_{20}x+M_{21}y+M_{22}},\frac{M_{10}x+M_{11}y+M_{12}}{M_{20}x+M_{21}y+M_{22}})

cv::getPerspectiveTransform()用于计算透视映射矩阵

cv::Mat cv::getPerspectiveTransform(const cv::Point2f* src, const cv::Point2f* dst)

作用:生成映射矩阵M。src和dst是四个二维(x,y)点的数组。

#include "stdafx.h"
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;

int main()
{
	cv::Mat src = cv::imread("D:\\personal-data\\wallpapers\\test.png");
	if (src.empty()) {
		return -1;
	}

	cv::Point2f srcQuad[] = { cv::Point2f(0, 0), cv::Point2f(src.cols - 1, 0), cv::Point2f(src.cols - 1, src.rows - 1), cv::Point2f(0, src.rows - 1) };
	cv::Point2f dstQuad[] = { cv::Point2f(src.cols * 0.05f, src.rows * 0.33f), cv::Point2f(src.cols * 0.9f, src.rows * 0.25f), cv::Point2f(src.cols * 0.8f, src.rows * 0.9f), cv::Point2f(src.cols * 0.2f, src.rows * 0.7f) };

	cv::Mat warp_mat = cv::getPerspectiveTransform(srcQuad, dstQuad);
	cv::Mat dst;
	cv::warpPerspective(src, dst, warp_mat, src.size(), cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar());
	for (int i = 0; i < 4; i++)
	{
		cv::circle(dst, dstQuad[i], 5, cv::Scalar(255, 0, 255), -1, cv::LINE_AA);
	}
	cv::imshow("dst", dst);
	cv::waitKey(0);
	cv::destroyAllWindows();
    return 0;
}

cv::perspectiveTransform()用于稀疏透视变换

 cv::perspectiveTransform()是一个特殊的函数,可以对点列阵表执行透视变换。因为cv::transform()被限定于线性运算,所有不能正确地处理透视变换。这是因为这种变换需要由同质表示的第三个座标(x=f * x / Z, y=f * y /Z)来划分,所以提供了cv::perspectiveTransform()函数:

void cv::perspectiveTransform(InputArray src, OutputArray dst, InputArray mtx)

作用:生成稀疏矩阵的透视变换。输入输出都是N×1,但是通道数可能是2或3,矩阵mtx可以是3×3(投影从二维到二维)或4×4(投影从三维到三维)的矩阵。

通用变换

极座标变换

cv::cartToPolar()用于将直角座标转换为极座标

void cv::cartToPolar(InputArray x, InputArray y, OutputArray magnitude, OutputArray angle, bool angleInDegrees=false)

原理:x,y(单通道)表示的并不仅仅是一系列的点,它还代表着向量场。该函数返回结果保存着数组的大小和角度,大小表示该点处x和y向量的长度,并且角度表示该向量的方向。如果angleInDegrees设置为true,则角度来保存,否则默认以弧度保存。角度值使用atan2(y, x)进行计算。

cv::polarToPolar()用于将极座标转换为直角座标

void cv::polarToPolar(InputArray magnitude,InputArray angle, OutputArray x, OutputArray y, bool angleInDegrees=false)

和cv::cartToPolar()正好相反,求逆的过程。

LogPolar

对于二维图像,对数-极座标变换是从直角座标变换为对数极座标,即(x,y)\leftrightarrow re^{i\theta },其中,r=\sqrt{x^2+y^2},\theta =atan2(y,x),然后将极座标表示为a(\rho ,\theta )形式。当我们需要把一些有趣的东西填充到目标图像的空白区域,通常的做法是将比例因子m应用于ρ。

cv::logPolar()函数

void cv::logPolar(InputArray src, OutputArray dst, cv::Point2f center, double m, int flags=cv::INTER_LINEAR | cv::WARP_FILL_OUTLIERS)

作用:进行对数-极座标转换。center是对数-极座标变换的中心点;m是比例因子,这个比例因子的设置原则应该便于最感兴趣的特征能够存在于图像的大部分区域。flags表示不同的插入方式,cv::WARP_FILL_OUTLIERS是附加选项(填充未定点),可组合使用。

#include "stdafx.h"
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;

int main()
{
	cv::Mat src = cv::imread("D:\\personal-data\\wallpapers\\test.png");
	if (src.empty()) {
		return -1;
	}
	
	double M = 100;
	cv::Mat dst(src.size(), src.type()); 
	cv::Mat src2(src.size(), src.type());

	cv::logPolar(src, dst, cv::Point2f(src.cols * 0.5f, src.rows * 0.5f), M, cv::INTER_LINEAR | cv::WARP_FILL_OUTLIERS);
	cv::logPolar(dst, src2, cv::Point2f(src.cols * 0.5f, src.rows * 0.5f), M, cv::INTER_LINEAR | cv::WARP_INVERSE_MAP);
	cv::imshow("image", src);
	cv::imshow("log-polar", dst);
	cv::imshow("inverse log-polar", src2);
	cv::waitKey(0);
	cv::destroyAllWindows();
        return 0;
}

运行结果:

原始图像

对数-极座标变换

对数-极座标逆变换


任意映射

cv::remap()用于常规图像重绘

void cv::remap(InputArray src, OutputArray dst, OutputArray map1, OutputArray map2, int interpolation=cv::INTER_:INEAR, int borderMode=cv::BORDER_CONSTANT, const cv::Scalar& borderValue=cv::Scalar())

作用:自定义映射,通常用来纠正校准立体图像。map1和map2分别表示原图像上需重新定位的任意一点的x和y的位置。要求:这些图应与原图像和目标图像保持尺寸相同,并且必须使用以下数据类型:cv::S16C2、cv::F32C1、cv::F32C2,函数将根据interpolation设置自动插值,其中插值方法不能为cv::INTER_AREA。

图像修复

图像修复

cv::inpaint(InputArray src, InputArray inpaintMask, OutputArray dst, double InpaintRadius, int flags)

这里src是一个8位,一维的灰度图像或者一个三维的彩色的待修复图像;inpaintMask是一个8位,与src大小相同的一维图像,且损坏区域被非0像素标记,inpaintMask的其他像素均被设置为0;dst是输出图像,它与src有相同大小和数量的维度;InpaintRadius是每个已渲染像素周围区域,这一区域将被分解成该像素的结果输出颜色;flags有两种不同的修复方法:cv::INPAINT_NS和cv::INPAINT_TELEA。

去噪

在许多应用中,噪声主要来源于低光条件的影响。在低光下,数字成像器的增益必须增加,结果噪声也被放大。这种噪声的特征随着孤立的像素,看起来太亮或太暗,但在彩色图像中也可能发生变色。

OpenCV中实现的去燥算法称为“快速非局部均值去燥”(FNLMD)。虽然简单的去噪算法基本上依赖于对其周边的各个像素进行平均,但是FNLMD的中心概念是在图像中的其他地方寻找类似的像素,再对其取平均值。在这种情况下,像素被认为是相似的像素,不是因为它的颜色或者强度相似,而是因为它在环境中是相似的。这里的关键是,许多图像包含重复的结构,因此即使像素被噪声破坏,也会有许多其他类似像素。

基于以像素p和大小s为中心的窗口B(p,s),进行类似像素的识别,在点的周围给定一个期望更新的窗口,我们可以将这个窗口与其他像素q周围的窗口进行比较。定义两窗口之间的平方距离如下:

d^2(B(p,s), B(q,s))=\frac{1}{3(2s+1)}\sum _{c=1}^3\sum _{j\in B(0,s)}(I_c(p+j)-I_c(q+j))^2

其中,c是色彩索引,Ic(P)是图像上点p在通道c的色彩强度,并且j的总和超过了patch元素。从这个平方距离,可以将权重分配给相对于当前正在更新的像素的每隔一个像素,该权重由下式计算:

w(p,q)=e^{-\frac{max(d^2-2\sigma ^2,0.0)}{h^2}}

其中,σ是噪声中预测的标准偏差(强度单位),而h是一个通用的过滤参数,可以确定补丁的速度随着平均距离从我们更新的补丁增长而变得无关紧要。一般来说,增加h的值将增加去除的噪声,但是会牺牲一些图像细节;降低h的值会保留细节,同时增加噪声。

通常情况下,考虑到选择与要更新的补丁相距较远(以像素的为单位)的点,返回结果会相应递减,因为在一定距离内,这样的补丁数量呈平方增加。因此,通常会定义一个称为“搜索窗口”的整体区域,只有搜索窗口中的修补程序有助于更新。然后使用给定的指数衰减权重函数,通过搜索窗口中所有其他像素的简单加权平均值来给出当前像素的更新。

FNLMD基础算法cv::fastNIMeansDenoising()

void cv::fastNIMeansDenoising(InputArray src, OutputArray dst, float h = 3, int templateWindowSize = 7, int searchWindowSize = 21)

templateWindowSize(用于比较的补丁尺寸)的补丁区域和衰减参数h从输入数组src计算出输出数组dst,并考虑searchWindowSize(允许范围内补丁的最大值)距离内的补丁。图像可以是一维、二维或三维的,但必须是cv::U8类型。

cv::fastNIMeansDenoising()的灰度图像的推荐值
噪声:σ 块大小:s 搜索窗口 衰减参数:h
0 < σ ≤ 15 3 × 3 21 × 21 0.40 · σ
15 < σ ≤ 30 5 × 5 21 × 21 0.40 · σ
30 < σ ≤ 45 7 × 7 35 × 35 0.35 · σ
45 < σ ≤ 75 9 × 9 35 × 35 0.35 · σ
75 < σ ≤ 100 11 × 11 35 × 35 0.30 · σ

FNLMD彩色图像算法cv::fastNIMeansDenoisingColored()

void cv::fastNIMeansDenoisingColored(InputArray src,OutputArray dst,float h=3,float hColor=3,int templateWindowSize = 7, int searchWindowSize = 21)

该函数只接受cv::U8C3类型的图像,虽然原则上可能直接将算法或多或少地应用于RGB图像,但实际上,最好将图像转换为不同的色彩空间进行计算。首先将图像转为LAB颜色空间,然后应用FNLMD算法,然后将结果转换回RGB。其主要优点是在颜色方面实际上有三个衰减参数。然而在RGB表示中,不太可能将其中任何一个设置为不同的值。但是在LAB空间中,为颜色分量指定不同的衰减参数对于亮度分量是很自然的。h用于亮度衰减参数,而新参数hColor用于颜色通道。一般来说,hColor的值会比h小很多。在大多数情况下,10是一个合适的值。

cv::fastNIMeansDenoisingColored()彩色图像的推荐值
噪声:σ 块大小:s 搜索窗口 衰减参数:h
0 < σ ≤ 25 3 × 3 21 × 21 0.55 · σ
25 < σ ≤ 55 5 × 5 35 × 35 0.40 · σ
55 < σ ≤ 100 7 × 7 35 × 35 0.35 · σ

FNLMD视频图像算法cv::fastNIMeansDenoisingMulti()和cv::fastNIMeansDenoisingColoredMulti()

void cv::fastNIMeansDenoisingMulti(InputArrayOfArrays srcImgs, OutputArray dst, int imgToDenoiseIndex, int temporalWindowSize, float h=3, int templateWindowSize = 7, int searchWindowSize = 21)

void cv::fastNIMeansDenoisingColoredMulti(InputArrayOfArrays srcImgs, OutputArray dst, int imgToDenoiseIndex, int temporalWindowSize, float h=3, float hColor=3, int templateWindowSize = 7, int searchWindowSize = 21)

这两个函数用于顺序图像,在这种情况下,很自然地想象出来当前帧以外的帧很可能包含去噪像素的有用信息。在大多数应用中,图像之间的噪声不会是恒定的,而信号可能会相似甚至相同。这两个函数期望一个图像数组,并通过imgToDenoiseIndex告知函数序列中哪个图像是要被去噪的。最后必须提供一个时间窗口,指示在去噪中使用的序列中的图像数量,该参数必须为奇数,隐含窗口始终以imgToDenoiseIndex为中心。

直方图均衡化

void cv::equalizeHist(const InputArray src, OutputArray dst)

作用:用于对比均衡,src和dst都必须是单通道图像,所以对于彩色图像,必须分通道逐一处理。

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章