上一篇推送中,为大家介绍了几种图像处理算法总结的方法,在本次推送中,二白继续为大家介绍余下的方法。
1.图像放大算法
图像放大有许多算法,其关键在于对未知像素使用何种插值方式。以下我们将具体分析几种常见的算法,然后从放大后的图像是否存在色彩失真,图像的细节是否得到较好的保存,放大过程所需时间是否分配合理等多方面来比较它们的优劣。
当把一个小图像放大的时候,比如放大400%,我们可以首先依据原来的相邻4个像素点的色彩值,按照放大倍数找到新的ABCD像素点的位置并进行对应的填充,但是它们之间存在的大量的像素点,比如p点的色彩值却是不可知的,需要进行估算。
图5原始图像的相邻4个像素点分布图
图6 图像放大4倍后已知像素分布图
1)最临近点插值算法(Nearest Neighbor)
最邻近点插值算法是最简单也是速度最快的一种算法,其做法是將放大后未知的像素点P,將其位置换算到原始影像上,与原始的邻近的4周像素点A,B,C,D做比较,令P点的像素值等于最靠近的邻近点像素值即可。如上图中的P点,由于最接近D点,所以就直接取P=D。
这种方法会带来明显的失真。在A,B中点处的像素值会突然出现一个跳跃,这就是出现马赛克和锯齿等明显走样的原因。最临近插值法唯一的优点就是速度快。
2)双线性插值算法(Bilinear Interpolation)
其做法是將放大后未知的像素点P,將其位置换算到原始影像上,计算的四個像素点A,B,C,D对P点的影响(越靠近P点取值越大,表明影响也越大),其示意图如下。
图7 双线性插值算法示意图
其具体的算法分三步: 第一步插值计算出AB两点对P点的影响得到e点的值。
图8 线性插值算法求值示意图
对线性插值的理解是这样的,对于AB两像素点之间的其它像素点的色彩值,认定为直线变化的,要求e点处的值,只需要找到对应位置直线上的点即可。换句话说,A,B间任意一点的值只跟A,B有关。
第二步,插值计算出CD两点对P点的影响得到f点的值。
第三步,插值计算出ef两点对P点的影响值。
双线性插值算法由于插值的结果是连续的,所以视觉上会比最邻近点插值算法要好一些,不过运算速度稍微要慢一点,如果讲究速度,是一个不错的折衷。
3)双立方插值算法(Bicubic Interpolation)
双立方插值算法与双线性插值算法类似,对于放大后未知的像素点P,将对其影响的范围扩大到邻近的16个像素点,依据对P点的远近影响进行插值计算,因P点的像素值信息来自16个邻近点,所以可得到较细致的影像,不过速度比较慢。
图 9双立方插值的附近4个临近点
好了,在介绍完了这些基础知识后,我们接下来讲解如何实现这些算法。
2.最临近点插值缩放
图10 最邻近点插值放大(2倍)处理效果
(由于Word版面原因,您看到的图像被word自动缩放成合适的宽度了)
最临近插值的的思想很简单。对于通过反向变换得到的的一个浮点坐标,对其进行简单的取整,得到一个整数型坐标,这个整数型坐标对应的像素值就是目的像素的像素值,也就是说,取浮点坐标最邻近的左上角点(对于DIB是右上角,因为它的扫描行是逆序存储的)对应的像素值。可见,最邻近插值简单且直观,但得到的图像质量不高。
下面给出算法的实现:
void CBMPSampleDlg::NearestNeighbourInterpolation(float fWRatio, float fHRatio)
{
//打开旧图像
CDib srcDib;
srcDib.AttachMapFile("1.bmp", TRUE);
//获取旧的高和宽
LONG lOldHeight = srcDib.m_lpBMIH->biHeight;
LONG lOldWidth = srcDib.m_lpBMIH->biWidth;
//保存旧的图像的字节
WORD wOldBitCount = srcDib.m_lpBMIH->biBitCount;
//计算新的高和宽
//四舍五入
LONG lNewHeight = LONG ( lOldHeight * fHRatio + 0.5 );
LONG lNewWidth = LONG ( lOldWidth * fWRatio + 0.5 );
CSize size;
size.cx = lNewWidth;
size.cy = lNewHeight;
//创建图像
m_Dib.CreatDib( size, wOldBitCount );
//获取旧图像每行字节数
LONG lOldLineBytes = WIDTHBYTES( lOldWidth * wOldBitCount );
//获取新图像每行的字节数
LONG lNewLineBytes = WIDTHBYTES( lNewWidth * wOldBitCount );
//计算填充的字节
int iNewFillBytes = lNewLineBytes - lNewWidth * ( wOldBitCount / 8 );
int iOldFillBytes = lOldLineBytes - lOldWidth * ( wOldBitCount / 8 );
//指定图像大小,新图像的大小有不同
m_Dib.m_dwSizeImage = lNewLineBytes * lNewHeight;
//分配空间
m_Dib.AllocImage();
//定位到数据的开头,也就是图像最后一行的行首
LPRGBTRIPLE DRgb = ( LPRGBTRIPLE ) m_Dib.m_lpImage;
LPRGBTRIPLE SRgb = ( LPRGBTRIPLE ) srcDib.m_lpImage;
//将每行的头指针存储起来
PRGBTRIPLE *pNewRow = (PRGBTRIPLE *)malloc(sizeof(PRGBTRIPLE) * lNewHeight);
PRGBTRIPLE * pOldRow = (PRGBTRIPLE *)malloc(sizeof(PRGBTRIPLE) * lOldHeight);
LONG srcCol = 0,srcRow = 0;
for (int row = 0 ; row < lNewHeight ; row ++)
{
pNewRow[row] = PRGBTRIPLE( (PBYTE)DRgb + row * lNewLineBytes );
}
for (int row = 0 ; row < lOldHeight ; row ++)
{
pOldRow[row] = PRGBTRIPLE ( (PBYTE)SRgb + row * lOldLineBytes );
}
for ( LONG row = 0 ; row < lNewHeight ; row ++ )
{
//对每一行进行处理
for ( LONG col = 0 ; col < lNewWidth ; col ++ )
{
//计算在源图像中的坐标srcCol 和srcRow
//四舍五入
srcCol = (LONG) floor(col / fWRatio);
srcRow = (LONG) floor(row / fHRatio);
//判断计算出来的坐标是否在源图像中
if ( ( ( srcCol >= 0 ) && ( srcCol < lOldWidth ) ) && ( ( srcRow >= 0 ) && ( srcRow < lOldHeight ) ) )
{
//定位指针到源图像的位置
//复制像素的值
*(pNewRow[row] + col) = *(pOldRow[srcRow] + srcCol);
}
}
}
//释放申请的内存
free( pNewRow );
free( pOldRow );
DrawPic();
m_Dib.CopyToMapFile("最邻近法插值.bmp");
}
图中的fWRatio, fHRatio分别为水平方向和垂直方向的缩放系数,如果大于1则为放大,如果大于0小于1则为缩小。
3.双线性插值缩放
图11 双线性插值放大(2倍)处理效果
(由于Word版面原因,您看到的图像被word自动缩放成合适的宽度了)
下面给出双线性插值的具体实现:
//双线性插值法
void CBMPSampleDlg::BiLinearInterpolation(float fWRatio, float fHRatio)
{
//打开旧图像
CDib srcDib;
srcDib.AttachMapFile("1.bmp", TRUE);
//获取旧的高和宽
LONG lOldHeight = srcDib.m_lpBMIH->biHeight;
LONG lOldWidth = srcDib.m_lpBMIH->biWidth;
//保存旧的图像的字节
WORD wOldBitCount = srcDib.m_lpBMIH->biBitCount;
//计算新的高和宽
//四舍五入
LONG lNewHeight = LONG ( lOldHeight * fHRatio + 0.5 );
LONG lNewWidth = LONG ( lOldWidth * fWRatio + 0.5 );
CSize size;
size.cx = lNewWidth;
size.cy = lNewHeight;
//创建图像
m_Dib.CreatDib( size, wOldBitCount );
//获取旧图像每行字节数
LONG lOldLineBytes = WIDTHBYTES( lOldWidth * wOldBitCount );
//获取新图像每行的字节数
LONG lNewLineBytes = WIDTHBYTES( lNewWidth * wOldBitCount );
//计算填充的字节
int iNewFillBytes = lNewLineBytes - lNewWidth * ( wOldBitCount / 8 );
int iOldFillBytes = lOldLineBytes - lOldWidth * ( wOldBitCount / 8 );
//指定图像大小,新图像的大小有不同
m_Dib.m_dwSizeImage = lNewLineBytes * lNewHeight;
//分配空间
m_Dib.AllocImage();
//定位到数据的开头,也就是图像最后一行的行首
LPRGBTRIPLE DRgb = ( LPRGBTRIPLE ) m_Dib.m_lpImage;
LPRGBTRIPLE SRgb = ( LPRGBTRIPLE ) srcDib.m_lpImage;
//将每行的头指针存储起来
PRGBTRIPLE *pNewRow = (PRGBTRIPLE *)malloc( sizeof(PRGBTRIPLE) * lNewHeight );
PRGBTRIPLE * pOldRow = (PRGBTRIPLE *)malloc( sizeof(PRGBTRIPLE) * lOldHeight );
for (int row = 0 ; row < lNewHeight ; row ++)
{
pNewRow[row] = PRGBTRIPLE( (PBYTE)DRgb + row * lNewLineBytes );
}
for (int row = 0 ; row < lOldHeight ; row ++)
{
pOldRow[row] = PRGBTRIPLE ( (PBYTE)SRgb + row * lOldLineBytes );
}
float p = 0 , q = 0;
LONG PACol = 0,PARow = 0;
PRGBTRIPLE PA = NULL ,PB = NULL , PC = NULL , PD = NULL , PDst = NULL;
for ( LONG row = 0 ; row < lNewHeight ; row ++ )
{
//对每一行进行处理
for ( LONG col = 0 ; col < lNewWidth ; col ++ )
{
//计算在源图像中的坐标
PACol = (LONG) floor( col / fWRatio );
PARow = (LONG) floor( row / fHRatio );
//计算P和Q
p = col / fWRatio - PACol;
q = row / fHRatio - PARow;
//判断计算出来的坐标是否在源图像中
if ( ( ( PACol >= 0 ) && ( PACol < lOldWidth -1 ) ) && ( ( PARow >= 0 ) && ( PARow < lOldHeight -1 ) ) )
{
//获得四周的像素指针
//PA即 指向A点的指针,如下图为各个点的分布
// A | C
// q
// -p Dst
//
// B D
//
PA = pOldRow[PARow] + PACol;
PB = pOldRow[PARow + 1] + PACol;
PC = pOldRow[PARow] + PACol + 1;
PD = pOldRow[PARow + 1] + PACol + 1;
//需要确定的像素的指针,在目标图像中
PDst = pNewRow[row] + col;
//对目标像素的分量进行计算
//Blue
PDst->rgbtBlue = BYTE( ( 1 - p ) * ( 1 - q ) * PA->rgbtBlue \
+ ( 1 - p ) * q * PB->rgbtBlue \ + p * ( 1- q ) * PC->rgbtBlue \
+ p * q * PD->rgbtBlue );
if(PDst->rgbtBlue < 0)
{
PDst->rgbtBlue = 0;
}
if (PDst->rgbtBlue > 255)
{
PDst->rgbtBlue = 255;
}
//Green
PDst->rgbtGreen = BYTE( ( 1 - p ) * ( 1 - q ) * PA->rgbtGreen \
+ ( 1 - p ) * q * PB->rgbtGreen \
+ p * ( 1- q ) * PC->rgbtGreen \
+ p * q * PD->rgbtGreen );
if(PDst->rgbtGreen < 0)
{
PDst->rgbtGreen = 0;
}
if (PDst->rgbtGreen > 255)
{
PDst->rgbtGreen = 255;
}
//Red
PDst->rgbtRed = BYTE( ( 1 - p ) * ( 1 - q ) * PA->rgbtRed \
+ ( 1 - p ) * q * PB->rgbtRed \
+ p * ( 1- q ) * PC->rgbtRed \
+ p * q * PD->rgbtRed );
if(PDst->rgbtRed < 0)
{
PDst->rgbtRed = 0;
}
if (PDst->rgbtRed > 255)
{
PDst->rgbtRed = 255;
}
}
}
}
free( pNewRow );
free( pOldRow );
DrawPic();
m_Dib.CopyToMapFile("双线性插值法.bmp");
}
图像处理算法到这里就告一段落了,六种方法,足以解决常见的一些图像处理算法问题。对初学者二白来说,这次知识量很大,在初步掌握这几种方法后,要多多复习哦。(不多说啦,小白师兄又给派活了,大家下期再见!)
本文转载自xizero00博客,并进行修改,仅用作学习交流,如有侵权,请联系后台删除 https://blog.csdn.net/xizero00/article/details/6631209
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有