几何变换
基本的图像几何变换方法主要包括:伸缩、旋转、翻转等。
1、伸缩
设原图像中像素的坐标为(x0, y0),对应映射到Resize后的目标图像中的坐标为(x1, y1),沿x和y方向缩放的比例分别为Ratiox(x1/x0)和Ratioy(y1/y0),那么二者满足如下关系:
对应的变换用矩阵表示为:
反过来,有:
最终,目标图像中的每一个像素位置(x1, y1)都可以在原图像中有相应的映射(x0, y0)(这里的x0, y0并不一定是整数,即不一定正对原图像中的像素点),根据原图像Psrc以及映射坐标(x0, y0)可以通过插值算法计算得到目标图像在(x1, y1)处的像素值Pdst(x1, y1):
其中为对应的插值算法,如最近邻算法,那么有:
举例:假设Ratiox=Ratioy=3,即图像放大3倍,那么目标图像在原图的映射关系如下图所示:
目标图像中x1=0,3,6在原图像中映射的值为x0=0,1,2,而x1=1,2,4,5,7,8映射的x0皆为小数,因此无法定位到具体的像素值,需要插值算法来计算。
程序设计思路:
遍历目标图像的所有坐标,根据目标图像坐标(x1, y1)计算映射在原图像中坐标(x0, y0),根据(x0, y0)使用插值算法从原图像计算出目标图像在(x1, y1)处的值。
实现代码:
/*****************************************************************************
函数名 : ImgResize
功能 : 图像缩放
算法实现 : <可选项>
参数说明 : tSrcImg 原图像[in]
ptDstImg 目标图像[out]
l32DstImgWidth 目标图像宽度[in]
l32DstImgHeight 目标图像高度[in]
返回值说明 : 无
其他说明 : 无
******************************************************************************/
void ImgResize(MyImage tSrcImg, MyImage *ptDstImg, l32 l32DstImgWidth, l32 l32DstImgHeight, u8 u8InterpType)
{
l32 l32SrcImgHeight = tSrcImg.l32ImgH;
l32 l32SrcImgWidth = tSrcImg.l32ImgW;
l32 l32ImgChs = tSrcImg.l32ImgChs;
f32 f32ScalRatioWidth = f32(l32SrcImgWidth) / f32(l32DstImgWidth); //宽度缩放比例
f32 f32ScalRatioHeight = f32(l32SrcImgHeight) / f32(l32DstImgHeight);//高度缩放比例
MyImageInit(ptDstImg, l32DstImgHeight, l32DstImgWidth, l32ImgChs);
for (l32 l32DstImgHeightIdx = 0; l32DstImgHeightIdx < l32DstImgHeight; l32DstImgHeightIdx++)
{
for (l32 l32DstImgWidthIdx = 0; l32DstImgWidthIdx < l32DstImgWidth; l32DstImgWidthIdx++)
{
/*计算目标图像中的像素映射在原图像中的坐标*/
f32 f32SrcImgHeightIdx = f32ScalRatioHeight * f32(l32DstImgHeightIdx);
f32 f32SrcImgWidthIdx = f32ScalRatioHeight * f32(l32DstImgWidthIdx);
for (l32 l32ImgChsIdx = 0; l32ImgChsIdx < l32ImgChs; l32ImgChsIdx++)
{
l32 l32DstImgPixelPos = l32DstImgHeightIdx * l32DstImgWidth * l32ImgChs + \
l32DstImgWidthIdx * l32ImgChs + l32ImgChsIdx; //目标图像像素在内存中的位置
ptDstImg->pu8Data[l32DstImgPixelPos] = ImgInterp(tSrcImg, f32SrcImgWidthIdx, f32SrcImgHeightIdx, l32ImgChsIdx, u8InterpType);
}
}
}
}
2、旋转
设原图像中点的坐标为(x0, y0),目标图像点的坐标为(x1, y1),以原点为中心旋转关系如下:
以图像中心为中心进行旋转,并且旋转后保留其全部像素(而非与原图像尺寸相同),过程如下:
程序设计思路:
1、创建一个未初始化的目标图像,目标图像高宽应大于等于图像旋转后的最大高宽(Hdst和Wdst可取原图像对角长度);
2、将目标图像移动至原点(即对于目标图像中的每个点(x1, y1)将其分别减去1/2长的目标图像宽和高,得到(x'1, y'1))
3、根据以原点为中心的旋转方法计算得到(x'1, y'1)在移动后的原图像(图中蓝色)中的映射点坐标(x'0, y'0)(由于目标图像可能比原图像大,所以(x'1, y'1)可能无法映射到原图像中,此时直接忽略这些点,保留其为初始化值,如0),最后将(x'0, y'0)移动到原图像位置(x'0和y'0分别加上原图像宽和高的1/2长度)
4、最后再根据(x0, y0)通过插值算法计算(x1, y1)处的像素值。
5、最后将多余的部分裁减掉(根据能够映射到原图像的xmax, ymax, xmin, ymin)。
实现代码:
/*****************************************************************************
函数名 : ImgRotate
功能 : 图像旋转
算法实现 : <可选项>
参数说明 : tSrcImg 原图像[in]
ptDstImg 目标图像[out]
f32Angle 旋转角度[in]
返回值说明 : 无
其他说明 : 无
******************************************************************************/
void ImgRotate(MyImage tSrcImg, MyImage *ptDstImg, f32 f32Angle, u8 u8InterpType)
{
f32 f32Radian = (f32Angle / 180.0) * PI;
f32 f32Cos = cos(f32Radian);
f32 f32Sin = cos(PI / 2.0 - f32Radian);
/*原图像高、宽、通道数*/
l32 l32SrcImgHeight = tSrcImg.l32ImgH;
l32 l32SrcImgWidth = tSrcImg.l32ImgW;
l32 l32ImgChs = tSrcImg.l32ImgChs;
/*目标图像高、宽*/
l32 l32DstImgHeight = 2 * sqrt((l32SrcImgHeight / 2)*(l32SrcImgHeight / 2) + (l32SrcImgWidth / 2)*(l32SrcImgWidth / 2));
l32 l32DstImgWidth = l32DstImgHeight;
f32 f32MinY = l32SrcImgHeight, f32MaxY = 0, f32MinX = l32SrcImgWidth, f32MaxX = 0;
MyImage ptRotaImg;
MyImageInit(&ptRotaImg, l32DstImgHeight, l32DstImgWidth, l32ImgChs);
MyImageInit(ptDstImg, l32DstImgHeight, l32DstImgWidth, l32ImgChs);
for (l32 l32DstImgHeightIdx = 0; l32DstImgHeightIdx < l32DstImgHeight; l32DstImgHeightIdx++)
{
for (l32 l32DstImgWidthIdx = 0; l32DstImgWidthIdx < l32DstImgWidth; l32DstImgWidthIdx++)
{
/*计算目标图像中的像素在原图像中的映射坐标*/
f32 f32SrcImgHeightIdx = -1.0 * (l32DstImgWidthIdx - 0.5 * l32DstImgWidth) * f32Sin + (l32DstImgHeightIdx - 0.5 * l32DstImgHeight) * f32Cos \
+ 0.5 * l32SrcImgHeight;
f32 f32SrcImgWidthIdx = (l32DstImgWidthIdx - 0.5 * l32DstImgWidth) * f32Cos + (l32DstImgHeightIdx - 0.5 * l32DstImgHeight) * f32Sin \
+ 0.5 * l32SrcImgWidth;
for (l32 l32ImgChsIdx = 0; l32ImgChsIdx < l32ImgChs; l32ImgChsIdx++)
{
l32 l32DstImgPixelPos = l32DstImgHeightIdx * l32DstImgWidth * l32ImgChs + \
l32DstImgWidthIdx * l32ImgChs + l32ImgChsIdx; //目标图像像素在内存中的位置
/*判断映射的原图像坐标是否越界*/
if (f32SrcImgHeightIdx <= l32SrcImgHeight && f32SrcImgHeightIdx >= 0 && f32SrcImgWidthIdx <= l32SrcImgWidth && f32SrcImgWidthIdx >= 0)
{
ptRotaImg.pu8Data[l32DstImgPixelPos] = ImgInterp(tSrcImg, f32SrcImgWidthIdx, f32SrcImgHeightIdx, l32ImgChsIdx, u8InterpType);
if (l32DstImgHeightIdx < f32MinY)
{
f32MinY = l32DstImgHeightIdx;
}
if (l32DstImgWidthIdx < f32MinX)
{
f32MinX = l32DstImgWidthIdx;
}
if (l32DstImgHeightIdx > f32MaxY)
{
f32MaxY = l32DstImgHeightIdx;
}
if (l32DstImgWidthIdx > f32MaxX)
{
f32MaxX = l32DstImgWidthIdx;
}
}
else
{
ptRotaImg.pu8Data[l32DstImgPixelPos] = 0;
}
}
}
}
ImgCrop(ptRotaImg, ptDstImg, l32(f32MinX), l32(f32MaxX), l32(f32MinY), l32(f32MaxY));
}
3、翻转
设原图像中点的坐标为(x0, y0),目标图像点的坐标为(x1, y1),图像高和宽分别为H和W,那么水平、垂直以及对角翻转后原图像与目标图像的像素点位置映射关系分别为:
程序设计思路:
遍历目标图像中的每个像素,其值取为:
实现代码:
/*****************************************************************************
函数名 : ImgFlip
功能 : 图像翻转
算法实现 : <可选项>
参数说明 : tSrcImg 原图像[in]
ptDstImg 目标图像[out]
u8FlipMode 翻转模式(FLIP_LEFTRIGHT 左右;FLIP_UPDOWN 上下;FLIP_DIAGONAL 对角)[in]
返回值说明 : 无
其他说明 : 无
******************************************************************************/
void ImgFlip(MyImage tSrcImg, MyImage *ptDstImg, u8 u8FlipMode)
{
/*原图像高、宽、通道数*/
l32 l32SrcImgHeight = tSrcImg.l32ImgH;
l32 l32SrcImgWidth = tSrcImg.l32ImgW;
l32 l32ImgChs = tSrcImg.l32ImgChs;
/*目标图像高、宽(等于原图像)*/
l32 l32DstImgHeight = l32SrcImgHeight;
l32 l32DstImgWidth = l32SrcImgWidth;
MyImageInit(ptDstImg, l32DstImgHeight, l32DstImgWidth, l32ImgChs);
for (l32 l32DstImgHeightIdx = 0; l32DstImgHeightIdx < l32DstImgHeight; l32DstImgHeightIdx++)
{
for (l32 l32DstImgWidthIdx = 0; l32DstImgWidthIdx < l32DstImgWidth; l32DstImgWidthIdx++)
{
l32 l32SrcImgHeightIdx, l32SrcImgWidthIdx;
switch (u8FlipMode)
{
case FLIP_UPDOWN: //上下翻转
l32SrcImgHeightIdx = l32DstImgHeight - 1 - l32DstImgHeightIdx;
l32SrcImgWidthIdx = l32DstImgWidthIdx;
break;
case FLIP_LEFTRIGHT: //左右翻转
l32SrcImgHeightIdx = l32DstImgHeightIdx;
l32SrcImgWidthIdx = l32DstImgWidth - 1 - l32DstImgWidthIdx;
break;
case FLIP_DIAGONAL:
l32SrcImgHeightIdx = l32DstImgHeight - 1 - l32DstImgHeightIdx;
l32SrcImgWidthIdx = l32DstImgWidth - 1 - l32DstImgWidthIdx;
break;
default:break;
}
for (l32 l32ImgChsIdx = 0; l32ImgChsIdx < l32ImgChs; l32ImgChsIdx++)
{
l32 l32DstImgPixelPos = l32DstImgHeightIdx * l32DstImgWidth * l32ImgChs + \
l32DstImgWidthIdx * l32ImgChs + l32ImgChsIdx; //目标图像像素在内存中的位置
l32 l32SrcImgPixelPos = l32SrcImgHeightIdx * l32SrcImgWidth * l32ImgChs + \
l32SrcImgWidthIdx * l32ImgChs + l32ImgChsIdx; //原图像像素在内存中的位置
ptDstImg->pu8Data[l32DstImgPixelPos] = tSrcImg.pu8Data[l32SrcImgPixelPos];
}
}
}
}
4、裁剪
设原图像中点的坐标为(x0, y0),目标图像点的坐标为(x1, y1),原图像高和宽分别为Hsrc和Wsrc,目标图像高和宽分别为Hdst和Wdst,裁剪框为((xmin, ymin),(xmax, ymax))从而有:
另外,有:
程序设计思路:
这里不需要按像素遍历,因为目标图像的每行像素在原图像中是按相同顺序连续存放的,所以只需要遍历目标图像每行,将其在原图像中的对应内存数据复制过来即可。
实现代码:
/*****************************************************************************
函数名 : ImgCrop
功能 : 图像裁剪
算法实现 : <可选项>
参数说明 : tSrcImg 原图像[in]
ptDstImg 目标图像[out]
(l32xmin, l32ymin) 左上角坐标[in]
(l32xmax, l32ymax) 右下角坐标[in]
返回值说明 : 无
其他说明 : 无
******************************************************************************/
u8 ImgCrop(MyImage tSrcImg, MyImage *ptDstImg, l32 l32xmin, l32 l32xmax, l32 l32ymin, l32 l32ymax)
{
/*原图像高、宽、通道数*/
l32 l32SrcImgHeight = tSrcImg.l32ImgH;
l32 l32SrcImgWidth = tSrcImg.l32ImgW;
l32 l32ImgChs = tSrcImg.l32ImgChs;
if (l32xmin >= l32xmax || l32ymin >= l32ymax)
{
fprintf(stderr, "l32xmin must be less than l32xmax or l32ymin less than l32ymax!");
return -1;
}
/*目标图像高、宽*/
l32 l32DstImgHeight = l32ymax - l32ymin;
l32 l32DstImgWidth = l32xmax - l32xmin;
MyImageInit(ptDstImg, l32DstImgHeight, l32DstImgWidth, l32ImgChs);
l32 l32MemSize = (l32xmax - l32xmin) * l32ImgChs * sizeof(u8);
for (l32 l32DstImgHeightIdx = 0; l32DstImgHeightIdx < l32DstImgHeight; l32DstImgHeightIdx++)
{
u8 *pu8SrcImgMemStart = tSrcImg.pu8Data + ((l32DstImgHeightIdx + l32ymin) * l32SrcImgWidth + l32xmin) * l32ImgChs;
u8 *pu8DstImgMemStart = ptDstImg->pu8Data + l32DstImgHeightIdx * l32DstImgWidth * l32ImgChs;
memcpy(pu8DstImgMemStart, pu8SrcImgMemStart, l32MemSize);
}
return 0;
}
5、说明
用于表示图像的自定义结构体为MyImage,其定义如下:
/*自定义图像结构体*/
typedef struct
{
u8 *pu8Data;
l32 l32ImgH;
l32 l32ImgW;
l32 l32ImgChs;
}MyImage;
其中,指针pu8Data指向存储像素值数据的内存。这里每个像素各通道的值的存储方式与OpenCV的Mat::data相同,即通道->列->行的方式,也就是按照第一行第一列像素的第一个通道值,第一行第一列像素的第二个通道值,第一行第一列像素的第三个通道值,第一行第二列像素的第一个通道值...的方式存储。那么第h行第w列像素的第c个通道的值(这里h、w、c皆从0开始)在该图像数据内存中的位置为: