嵌牛導(dǎo)讀:本文通過矩陣的變化用c++算法實現(xiàn)了對圖像的處理。
嵌牛鼻子:圖像的幾何變換
嵌牛問題:如何保證變化過程中數(shù)據(jù)與數(shù)據(jù)類型不發(fā)生變化。
轉(zhuǎn)自: https://blog.csdn.net/qq_34784753/article/details/56004248?utm_source=app&app_version=4.8.0&code=app_1562916241&uLinkId=usr1mkqgl919blen
嵌牛正文
圖像的幾何變換是指在不改變圖像像素的前提下對圖像像素進(jìn)行空間幾何變換。常見的變換有距離變換,坐標(biāo)映射,平移,鏡像,旋轉(zhuǎn),縮放和仿射變換等等。
也就是說,圖像的幾何變換就是建立一種源圖像像素與變換后的圖像像素之間的映射關(guān)系。也正是通過這種映射關(guān)系可以知道原圖像任意像素點變換后的坐標(biāo),或者是變換后的圖像在原圖像的坐標(biāo)位置等。用簡單的數(shù)學(xué)公式可以表示為
其中,x,y代表輸出圖像像素的坐標(biāo),x0,y0表示輸入圖像的像素坐標(biāo),而U,V表示的是兩種映射關(guān)系,需要說明的是,映射關(guān)系可以是線性關(guān)系,也可以是多項式關(guān)系
從上面的映射關(guān)系可以看到,只要給出了圖像上任意的像素坐標(biāo),都能夠通過對應(yīng)的映射關(guān)系獲得幾何變換后的像素坐標(biāo)。這種將輸入映射到輸出的過程我們稱之為“向前映射”。但是在實際應(yīng)用中,向前映射會出現(xiàn)如下幾個問題:
a.浮點數(shù)坐標(biāo),如(1,1)映射為(0.5,0.5),顯然這是一個無效的坐標(biāo),這時我們可以使用插值算法進(jìn)行進(jìn)一步處理。
b.映射不完全和映射重疊。
映射不完全是說輸入圖像的像素總數(shù)小于輸出的像素總數(shù),這會使得輸出圖像的部分像素與原始圖像并沒有映射關(guān)系,如進(jìn)行放大操作。映射重疊是與映射不完全正好相反,輸出圖像會存在映射后的像素重疊。
為了克服前向映射的這些不足,因此引進(jìn)了“后向映射”,它的數(shù)學(xué)表達(dá)式為:
同樣的,x,y表示輸出圖像像素的坐標(biāo),x0,y0表示輸入圖像像素的坐標(biāo),U'和V'表示兩種映射方式。
可以看出,后向映射與前向映射剛好相反,它是由輸出圖像的像素坐標(biāo)反過來推算該像素為在源圖像中的坐標(biāo)位置。這樣,輸出圖像的每個像素值都能夠通過這個映射關(guān)系找到對應(yīng)的為止。而不會造成上面所提到的映射不完全和映射重疊的現(xiàn)象。
在實際處理中基本上都運用向后映射來進(jìn)行圖像的幾何變換。
在使用過程中,如果在一些不改變圖像大小的幾何變換中,向前映射還是十分有效的,向后映射主要運用在圖像的旋轉(zhuǎn)的縮放中,因為這些幾何變換都會改變圖像的大小。
在本篇文章里圖像的幾何變換全部都采用統(tǒng)一的矩陣表示法,形式如下:
這就是向前映射的矩陣表示法,其中x,y表示輸出圖像像素的坐標(biāo),x0,y0表示輸入圖像像素的坐標(biāo)
同理,向后映射的矩陣表示為:
可以證明,向后映射的矩陣的表示正好是向前映射的逆變換。
4.1坐標(biāo)映射。
4.1這部分文字內(nèi)容一部分參考了朱偉等主編的《OpenCV圖像處理編程實例》
圖像的坐標(biāo)映射是通過原圖像與目標(biāo)圖像與目標(biāo)圖像之間建立一種映射關(guān)系,這種映射關(guān)系有兩種,也就是上面所提到的向前映射和向后映射。
在OpenCV中提供了重映射相關(guān)的操作,而對于映射后出現(xiàn)了目標(biāo)圖像像素是非整數(shù)的情況,一般可以考慮插值或是向上取整。
void remap( InputArray src, OutputArray dst, InputArray map1, InputArray map2,int interpolation, borderMode=BORDER_CONSTANT,const Scalar& borderValue=Scalar());
這個函數(shù)的主要作用是進(jìn)行圖像的重映射操作,參數(shù) src 和 dst 分別表示輸入原圖像和映射后的圖像,參數(shù) map1 表示(x,y)點的坐標(biāo)或x坐標(biāo),可以是CV_16SC2,CV_32FC1或者CV_32FC2類型, map2 表示y坐標(biāo),可以使CV_16UC1,CV_32FC1類型,如果 map1 為(x,y),則 map2 可以不使用, interpolation 表示使用的插值方法,有四種可以選擇,
·? ? INTER_NEAREST -最近鄰插值
·? ? INTER_LINEAR –雙線性插值(默認(rèn)值)
·? ? INTER_CUBIC –雙三次樣條插值(逾4×4像素鄰域內(nèi)的雙三次插值)
·? ? INTER_LANCZOS4 -Lanczos插值(逾8×8像素鄰域的Lanczos插值)
boderMode表示邊界插值的類型,有默認(rèn)值BORDER_CONSTANT,表示目標(biāo)圖像中“離群點(outliers)”的像素值不會被此函數(shù)修改。
boderValue表示插值數(shù)值,其有默認(rèn)值Scalar( ),即默認(rèn)值為0。
使用OpenCV實現(xiàn)圖像的坐標(biāo)映射相關(guān)代碼如下:
#include <iostream>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat srcImage = imread("2345.jpg");
if (!srcImage.data)
{
cout << "讀入圖片錯誤!" << endl;
return -1;
}
imshow("原始圖", srcImage);
//創(chuàng)建輸出矩陣
Mat dstImage(srcImage.size(), srcImage.type());
//定義x和y方向的矩陣
Mat xMap(srcImage.size(), CV_32FC1);
Mat yMap(srcImage.size(), CV_32FC1);
//獲取圖像的寬和高
int rowNumber = srcImage.rows;
int colNumber = srcImage.cols;
//對圖像進(jìn)行遍歷操作
for (int i = 0; i < rowNumber; i++)
{
for (int j = 0; j < colNumber; j++)
{
//x和y都進(jìn)行翻轉(zhuǎn)操作
xMap.at<float>(i, j) = static_cast<float>(srcImage.cols - j);
yMap.at<float>(i, j) = static_cast<float>(srcImage.rows - i);
}
}
//進(jìn)行重映射操作
remap(srcImage, dstImage, xMap, yMap,
CV_INTER_LINEAR, BORDER_CONSTANT, Scalar(0, 0, 0));
imshow("映射效果圖", dstImage);
waitKey();
return 0;
}
運行后效果圖為:
可以看出,圖像上下和左右都進(jìn)行了翻轉(zhuǎn)操作。
4.2 平移變換
圖像的平移變換是最簡單的幾何變換,就是將圖像中所有像素的坐標(biāo)分別加上或減去指定的水平和垂直偏移量,從而使整張圖片出現(xiàn)移位的效果。
對于原始圖像而言,它的正變換矩陣為:
而對于目標(biāo)圖像而言,其逆變換矩陣為:
平移變換用程序?qū)崿F(xiàn)過程如下:
//實現(xiàn)圖像的平移變換
#include <iostream>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat srcImage, dstImage;
int xOffset, yOffset; //x和y方向的平移量
srcImage = imread("2345.jpg");
if (!srcImage.data)
{
cout << "讀入圖片錯誤!" << endl;
return -1;
}
dstImage.create(srcImage.size(), srcImage.type());
cout<< "請輸入x方向和y方向的平移量:";
cin >> xOffset >> yOffset;
int rowNumber = srcImage.rows;
int colNumber = srcImage.cols;
//進(jìn)行遍歷圖像
for (int i = 0; i < rowNumber; i++)
{
for (int j = 0; j < colNumber; j++)
{
//平移變換
int x = j - xOffset;
int y = i - yOffset;
//判斷邊界情況
if (x >= 0 && y >= 0 && x < colNumber && y < rowNumber)
dstImage.at<Vec3b>(i, j) = srcImage.at<Vec3b>(y,x);
}
}
imshow("原圖像", srcImage);
imshow("平移后的圖像", dstImage);
waitKey();
return 0;
}
程序效果如下圖所示:
這里x和y方向的平移量都取+50個像素
4.3 鏡像變換
圖像的鏡像變換和數(shù)學(xué)上的軸對稱非常類似,水平鏡像和垂直鏡像分別對應(yīng)著以圖像的水平中軸線和垂直中軸線為對稱軸做對稱變換操作。
總之,水平鏡像變換產(chǎn)生的是原始圖像的水平投影,類似于在鏡子中顯示的物體,而垂直鏡像變換是原始圖在垂直方向上進(jìn)行投影,效果類似于水中的倒影。
基本原理:
a.水平鏡像變換
設(shè)圖像的寬度是width,則水平鏡像變換的映射關(guān)系如下:
用矩陣可以表示為:
相應(yīng)的逆運算矩陣如下:
可以發(fā)現(xiàn),水平鏡像變換的向前映射和向后映射的兩個關(guān)系式相同,也就是說,將水平鏡像變換得到的結(jié)果再做水平變化會得到原來的圖像,從數(shù)學(xué)的角度上思考,這是顯然的。同理,在垂直鏡像變換中也有這樣的結(jié)論。
b.垂直鏡像變換
設(shè)變換的圖像的高度為height,垂直鏡像變換的映射關(guān)系如下:
使用矩陣可以表示為:
相應(yīng)的逆運算為:
下面使用OpenCV和C++語言編程實現(xiàn)水平鏡像變換和垂直鏡像變換過程。
需要說明的是,在OpenCV中有flip函數(shù)可以實現(xiàn)鏡像變換功能,函數(shù)說明如下:
void flip(InputArray src, OutputArray dst, int flipCode)
其中, src表示輸入圖像, dst表示輸出圖像, flipCode表示翻轉(zhuǎn)模式, flipCode==0垂直翻轉(zhuǎn)(沿X軸翻轉(zhuǎn)),flipCode>0水平翻轉(zhuǎn)(沿Y軸翻轉(zhuǎn)),flipCode<0水平垂直翻轉(zhuǎn)(先沿X軸翻轉(zhuǎn),再沿Y軸翻轉(zhuǎn),等價于旋轉(zhuǎn)180°)
//實現(xiàn)圖像的鏡像變換
//包括水平鏡像和豎直鏡像
#include <iostream>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat srcImage, dstImage1,dstImage2;
srcImage = imread("2345.jpg");
dstImage1.create(srcImage.size(), srcImage.type());
dstImage2.create(srcImage.size(), srcImage.type());
//方法1,使用flip函數(shù)對圖像進(jìn)行水平鏡像變換操作
flip(srcImage, dstImage1, 1); //第三個參數(shù)flipCode>0表示沿y軸做鏡像
//方法2,遍歷圖像像素
int rowNumber = srcImage.rows;
int colNumber = srcImage.cols;
for (int i = 0; i < rowNumber; i++)
{
for (int j = 0; j < colNumber; j++)
{
dstImage2.at<Vec3b>(i, j)[0] = srcImage.at<Vec3b>(i, colNumber - j - 1)[0];
dstImage2.at<Vec3b>(i, j)[1] = srcImage.at<Vec3b>(i, colNumber - j - 1)[1];
dstImage2.at<Vec3b>(i, j)[2] = srcImage.at<Vec3b>(i, colNumber - j - 1)[2];
}
}
imshow("原圖像", srcImage);
imshow("flip方法水平鏡像", dstImage1);
imshow("遍歷像素水平鏡像", dstImage2);
//方法1,使用flip函數(shù)對元對象進(jìn)行垂直鏡像變換操作
flip(srcImage, dstImage1, 0);
//方法2,遍歷圖像像素
for (int i = 0; i < rowNumber; i++)
{
for (int j = 0; j < colNumber; j++)
{
dstImage2.at<Vec3b>(i, j)[0] = srcImage.at<Vec3b>(rowNumber - i - 1, j)[0];
dstImage2.at<Vec3b>(i, j)[1] = srcImage.at<Vec3b>(rowNumber - i - 1, j)[1];
dstImage2.at<Vec3b>(i, j)[2] = srcImage.at<Vec3b>(rowNumber - i - 1, j)[2];
}
}
imshow("flip方法垂直鏡像", dstImage1);
imshow("遍歷像素垂直鏡像", dstImage2);
waitKey();
return 0;
}
程序效果如下:
圖像幾何變換的上半部分就