1.函数说明
cvLoadImage( filename, -1 );默认读取图像的原通道数
cvLoadImage( filename, 0 );强制转化读取图像为灰度图
cvLoadImage( filename, 1 );读取彩色图
IplImage*cvLoadImage()支持BMP,JPEG,PNG,PBM,PGM,PPM,SR, TIFF格式的图片
Main函数中关于参数intargc, char ** argv的说明:
argc 是指命令行输入参数的个数,argv存储了所有的命令行参数。
Examp1:图像的读入和显示
#include<opencv/highgui.h>
int main( int argc, char* argv[] )
{
IplImage* pImg; //声明pImg指针
if( argc==1&&(pImg= cvLoadImage( "C:/5.jpg", 1)) !=0 )//判断图像存在?
{
cvNamedWindow( "Image",1 );//创建窗口
cvShowImage( "Image",pImg );//显示图像
cvWaitKey(0); //等待按键
cvDestroyWindow( "Image" );//销毁窗口
cvReleaseImage( &pImg ); //释放窗口
return0;
}
return -1;
}
2.函数说明
IplImage* cvCreateImage( CvSize size, int depth, intchannels );
cvGetSize()返回矩阵或图像的行数和列数。
cvSaveImage:
examp2:图像的复制
#include<opencv/highgui.h>
int main( int argc, char* argv[] )
{
IplImage* pImg;
if( argc == 1&&
(pImg = cvLoadImage( "c:/2.jpg", 1)) != 0 )
{
IplImage* pImg2 =cvCreateImage(cvGetSize(pImg),
pImg->depth,
pImg->nChannels);
cvCopy(pImg, pImg2, NULL);
cvSaveImage("c:/n.jpg",pImg2);
cvNamedWindow( "Image",1 );
cvShowImage( "Image",pImg );
cvWaitKey(0);
cvDestroyWindow( "Image");
cvReleaseImage( &pImg );
cvReleaseImage( &pImg2 );
return 0;
}
return -1;
}
图像反转:
图像是有一个个像素组成,每个像素有不同的RBG值,比如121,122,95,分别代表不同的色深。所有的颜色都能由这三个数表示。最大值分别为255。图像发转就是用255减去这三个值。 比如白色是255,255,255,那么反转后就是黑色,0,0,0。
opencv中代码data[i*step+j*channels+k]=255-data[i*step+j*channels+k];为什么data[]中要那样相加?
Img->imageData是指向图片数据的指针,如果是灰度图,图片数据每一位就是一个字节,按行排列存在这个数组里面,所以你要访问某一个像素点的时候,就要通过指针来访问,i代表像素所在行,img->imageData+i*img->widthStep就是指向第i行的首个像素地址,[j]代表的是第j个像素,也就是列。整个语句的意思就是指向第i行第j列,也就是坐标是(i,j)这个像素,给这个像素赋值111。
cvZero();是让矩阵的值都为0,有初始化的作用,或者说清零~
比如说:IplImage*img=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1);%创建一幅图像
cvZero(img);%相当于初始化图片,值都为0,矩阵大小为640*480
cvShowImage("img",img);%就显示一幅黑色,且值都为零的图像
问题1:矩阵处理是总是有错。
Ps1:彩色图像转换为灰度图像。
#include<opencv/highgui.h>
#include<opencv/cv.h>
int main(int argc,char*argv[])
{
IplImage*image;
IplImage*image1;
if(argc==1&&
(image=cvLoadImage("c:/1.jpg",1))!=0)
{
cvNamedWindow("exam",CV_WINDOW_AUTOSIZE);
image1=cvCreateImage(cvSize(image->width,image->height),IPL_DEPTH_8U,1);
cvCvtColor(image,image1,CV_BGR2GRAY);
cvShowImage("exam",image1);
cvWaitKey(0);
cvDestroyWindow("exam");
cvReleaseImage(&image1);
return0;
}
return -1;
}
Ps2:数组的输入与输出:
#include<opencv/highgui.h>
#include<opencv/cv.h>
void PrintMat(CvMat *A)
{
int i,j;
for(i=0;i<A->rows;i++)
{
for(j=0;j<A->cols;j++)
printf("%9.3f",(float)cvGetReal2D(A,i,j));
printf("\n");
}
return ;
}
int main(int argc,char*argv[])
{
double a[]={1,2,3,
4,5,6};
CvMat Ma;
cvInitMatHeader(&Ma,2,3,CV_64FC1,a);
PrintMat(&Ma);
system("pause");
return 0;
Ps3:图像载入并进行平滑处理:
图像平滑是指用于突出图像的宽大区域、低频成分、主干部分或抑制图像噪声和干扰高频成分,使图像亮度平缓渐变,减小突变梯度,改善图像质量的图像处理方法。
图像平滑的方法包括:插值方法,线性平滑方法,卷积法等等。这样的处理方法根据图像噪声的不同进行平滑,比如椒盐噪声,就采用线性平滑方法!
#include<opencv/highgui.h>
#include<opencv/cv.h>
int main(int argc,char*argv[])
{
IplImage*inimage;
if(argc==1&&
(inimage=cvLoadImage("c:/psb.jpg",1))!=0)
{
cvNamedWindow("exin",CV_WINDOW_AUTOSIZE);
cvNamedWindow("out",CV_WINDOW_AUTOSIZE);
cvShowImage("exin",inimage);
IplImage*outimage;
outimage=cvCreateImage(cvSize(inimage->width,inimage->height),IPL_DEPTH_8U,3);
cvSmooth(inimage,outimage,CV_GAUSSIAN,3,3);
cvShowImage("out",outimage);
cvWaitKey(0);
cvReleaseImage(&inimage);
cvReleaseImage(&outimage);
cvDestroyWindow("exin");
cvDestroyWindow("out");
return0;
}
return -1;
}
Ps4:使用cvPyrDown()输出与输入图像一半(2倍)的图像。
#include<opencv/highgui.h>
#include<opencv/cv.h>
int main(int argc,char*argv[])
{
IplImage*inimage;
if(argc==1&&
(inimage=cvLoadImage("c:/psb.jpg",1))!=0)
{
cvNamedWindow("exin",CV_WINDOW_AUTOSIZE);
cvNamedWindow("out",CV_WINDOW_AUTOSIZE);
cvShowImage("exin",inimage);
IplImage*outimage;
outimage=cvCreateImage(cvSize(inimage->width/2,inimage->height/2),inimage->depth,inimage->nChannels);
cvPyrDown(inimage,outimage);/*cvpyrUp(inimage,outimage);*/
cvShowImage("out",outimage);
cvWaitKey(0);
cvReleaseImage(&inimage);
cvReleaseImage(&outimage);
cvDestroyWindow("exin");
cvDestroyWindow("out");
return0;
}
return -1;
}
Ps5:使用cvCanny进行边缘检测。
问题:threshold1,threshold2的值是什么意思?
Ps6:矩阵初始化及矩阵元素的读取;cvGetReal2D(A,I,j),cvmGet(A,i,j);
矩阵中的某个元素的初始化:cvmSet(A,i,j,DD)
Ps7:widthstep是指图像每行所占的字节数,主要要和width区别,width是指每行所含的像素个数,但是一个像素也可能占一个字节,也可能占三个字节或者四个。imagedata是指向存储图像像素值数组的指针,内容是这个数组的首地址,pt.y指的是像素点的行坐标,所以Img->imageData + Img->widthStep*pt.y便是该像素点所在行的首地址,然后再加上该像素点所在的列,即pt.x,就得到了该像素点的地址,所以你的那句代码也可以写成
((uchar*)(Img1->imageData + Img1->widthStep*pt.y+pt.x)),都是指该像素点的像素值,至于强制类型转换,是因为会产生一些负值,而像素值是不能为负的。
C语言库函数名: atoi
功 能: 把字符串转换成整型数.
名字来源:array to integer 的缩写.
原型: int atoi(const char *nptr);
函数说明: 参数nptr字符串,如果第一个非空格字符不存在或者不是数字也不是正负号则返回零,否则开始做类型转换,之后检测到非数字(包括结束符 \0) 字符时停止转换,返回整型数。
头文件: #include <stdlib.h>
1. 函数名: atof
功 能: 把字符串转换成浮点数
名字来源:array to floating point numbers 的缩写
用 法: double atof(const char *nptr);
图像的ROI(Regionof Interest)是我们经常用到的,OpenCV提供了两种方式以便我们访问ROI:Image ROI和widthStep,从应用上来讲ROI当然比较方便,但ROI必须串行处理并且不但的设置和重置,想在操作中设置和保持一幅图像的多个子区域处于活动状态就需要widthStep。
坐图为书中3-12运行的结果:
#include "stdafx.h" #include "cv.h" #include "highgui.h"
int _tmain(int argc, _TCHAR*argv[]) { IplImage* src; if(argc==7 && ((src=cvLoadImage(argv[1]))!=0)) { int x= atoi(argv[2]); int y=atoi(argv[3]); int width=atoi(argv[4]); int height=atoi(argv[5]); int add=atoi(argv[6]); cvSetImageROI(src,cvRect(x,y,width,height)); cvAddS(src,cvScalar(add),src); cvResetImageROI(src); cvNamedWindow("ROI_Add",1); cvShowImage("ROI_Add",src); cvWaitKey(); } return 0; }
运行时,在命令行中输入7个参数:程序名,原始图像名,ROI的起点X值,起点Y值,ROI的宽度,高度,像素递增值。左下图后5个值以此为:150,150,300,300,250.
图像指定区域的图像处理:
#include<opencv/highgui.h>
#include<opencv/cv.h>
int main(int argc,char*argv[])
{
IplImage* src;
if(argc==1&& ((src=cvLoadImage("c:/1.jpg",1))!=0))
{
int x=150;
int y=150;
intwidth=300;
intheight=300;
int add=250;
cvSetImageROI(src,cvRect(x,y,width,height));
cvAddS(src,cvScalar(0,0,add),src);
/*cvResetImageROI(src);*/
cvNamedWindow("ROI_Add",1);
cvShowImage("ROI_Add",src);
cvWaitKey();
}
system("pause");
return 0;
}
像素位深度是指每个像素所用的位数(bit),像素位深度决定了彩色图像的每个像素可能有的颜色数,或者确定灰度图像的每个像素可能有的灰度级数。例如,一幅彩色图像的每个像素用R、G、B三个分量来表示,若每个分量用8位,那么一个像素共用24位表示,就说像素的深度为24位,每个像素可以是224,即16777216〔千万级〕种颜色中的一种。在这个意义上, 往往把像素的位深度说成是图像深度。表示一个像素的位数越多,它能表达的颜色数目就越多, 而它的深度就越深。虽然像素位深度或图像深度可以很深,但由于设备本身的限制,加上人眼自身分辨率的局限,一般情况下,一味追求特别深的像素深度没有意义。因为,像素深度越深,数据量越大,所需要的传输带宽及存储空间就越大。相反,如果像素深度太浅,会影响图像的质量,图像看起来让人觉得很粗糙而不自然。
提示:假如像素位深度是8(bit),那么以虚线框中4个像素点而言,以4 :2:0格式为例,釆样总共为6个采样点(4个亮度分量加2个色度分量),总共需要6×8=48比特,平均每个像素48/4=12比特,这就是为什么有些情况下4 :2:0采样格式也被称为“12比特每像素采样”的原因。
如果imageData指向的空间是由cvCreateImage初始化的,那么用cvReleaseImage来释放;如果imageData指向的空间是后来用户指定的,那么用vReleaseImageheader(&iplImage);释放,并由用户自行释放分配的空间。
一个很重要概念叫图像通道,在RGB色彩模式下就是指那单独的红色、绿色、蓝色部分。也就是说,一幅完整的图像,是由红色绿色蓝色三个通道组成的。
Opencv写字程序:
#include<opencv/cv.h>
#include<opencv/highgui.h>
char wndname[]="line";
int main(int argc,char*argv[])
{
int i,j;
CvFont font;
CvPoint pt1,pt2;
CvRNG rng=cvRNG(-1);
intwidth=1000;
intheight=700;
intwidth3=width*3;
intheight3=height*3;
IplImage*img=cvCreateImage(cvSize(1000,700),8,3);
cvNamedWindow(wndname,1);
cvZero(img);
cvShowImage(wndname,img);
for(i=0;i<255;i++)
{
pt1.x=width/2-250;
pt1.y=height/2;
cvInitFont(&font,CV_FONT_HERSHEY_SIMPLEX,3,3,0.0,5,CV_AA);
cvPutText(img,"Loveyou!",pt1,&font,CV_RGB(125,i,100));
cvShowImage(wndname,img);
cvWaitKey(10);
}
cvWaitKey(0);
cvReleaseImage(&img);
cvDestroyWindow(wndname);
return0;
}
#include <stdio.h>
#include <opencv/cv.h>
#include <opencv/highgui.h>
void saturate_sv( IplImage* img ) {
for( int y=100; y<200; y++ ) {
uchar* ptr = (uchar*) (
img->imageData + y * img->widthStep
);
for( int x=100; x<200; x++ ) {
ptr[3*x+0] = 0;
ptr[3*x+1] = 0;
ptr[3*x+2] = 0;
}
}
}
int main( int argc, char** argv )
{
IplImage* img = cvLoadImage( "c:/1.jpg ");
cvNamedWindow("Example1",CV_WINDOW_AUTOSIZE );
saturate_sv(img);
cvShowImage("Example1",img );
cvWaitKey(0);
cvReleaseImage( &img );
cvDestroyWindow("Example1");
}
图像数据的存储:
均值滤波:
#include <opencv/highgui.h>
#include <opencv/cv.h>
#include<stdio.h>
IplImage* meanfilter(const IplImage* psrcimage,intnwindowsize)
{
int nradius = (nwindowsize-1)/2;
if (psrcimage->nChannels!=1)
{
printf("images with more than one channel are notsurpported yet.^_^", "error");
system("pause");
//handleerror here
exit(0);
}
//handleimage with one channel
IplImage* pdesimage =cvCloneImage(psrcimage);
int nsumwindow = 0;
for (int i = nradius; i < psrcimage->height-nradius;i++)
{
for(int j = nradius; j <psrcimage->widthStep-nradius; j++)
{
nsumwindow = 0;
for(int m = -nradius; m <= nradius; m++)
{
for (int n =-nradius; n <= nradius; n++)
{
nsumwindow = nsumwindow + (unsigned char)(psrcimage->imageData[psrcimage->widthStep*(i+m)+(j+n)]);
}
}
pdesimage->imageData[pdesimage->widthStep*i+ j] = (unsigned char)(nsumwindow/(nwindowsize*nwindowsize));
}
}
return pdesimage;
}
int main( int argc, char** argv)
{
IplImage* img = cvLoadImage( "c:/psb.jpg ",1);
IplImage* img1 =cvCreateImage(cvSize(img->width,img->height),img->depth,1);
cvCvtColor(img,img1,CV_BGR2GRAY);
cvNamedWindow("Example1",CV_WINDOW_AUTOSIZE );
cvShowImage("Example1",meanfilter(img1,3));
cvWaitKey(0);
cvReleaseImage( &img1 );
cvDestroyWindow("Example1");
system("pause");
return 0;
}
#include<opencv/highgui.h>
#include<opencv/cv.h>
#include<stdio.h>
IplImage*MidFilter(IplImage*image,int nwindow);
int main(int argc,char*argv[])
{
IplImage*img;
if(argc==1&&
(img=cvLoadImage("C:/psb.jpg",1))!=0)
{
IplImage*imag;
imag=cvCreateImage(cvSize(img->width,img->height),img->depth,1);
cvCvtColor(img,imag,CV_BGR2GRAY);
if(imag->nChannels!=1)
{
printf("error\n");
system("pause");
exit(0);
}
MidFilter(imag,3);
cvNamedWindow("中D值¦Ì滤?波¡§",1);
cvShowImage("中D值¦Ì滤?波¡§",imag);
cvWaitKey(0);
cvReleaseImage(&imag);
cvDestroyWindow("中D值¦Ì滤?波¡§");
system("pause");
return 0;
}
return -1;
}
IplImage*MidFilter(IplImage*image,int nwindow)
{
intnsize=(nwindow-1)/2;
for(int i=nsize;i<image->height-nsize;i++)
{
for(int j=nsize;j<image->width-nsize;j++)
{
intnmidwindow=0;
unsignedchar temp;
for(int m=-nsize;m<=nsize;m++)
{
for(int n=-nsize;n<=nsize;n++)
{
if(image->imageData[image->widthStep*(i+m)+(n+j)]<image->imageData[image->widthStep*(i+m)+(n+j+1)])
{
temp=(image->imageData[image->widthStep*(i+m)+(n+j)]);
image->imageData[image->widthStep*(i+m)+(n+j)]=image->imageData[image->widthStep*(i+m)+(n+j+1)];
image->imageData[image->widthStep*(i+m)+(n+j+1)]=temp;
}
}
}
image->imageData[image->widthStep*i+j]=(unsigned char)(image->imageData[image->widthStep*i+j]);
}
}
return image;
}
中值滤波:
#include<opencv/highgui.h>
#include<opencv/cv.h>
#include<stdio.h>
IplImage*MidFilter(IplImage*image,int nwindow);
unsigned char medvalue(unsigned char* array,int nlength);
int main(int argc,char*argv[])
{
IplImage*img;
if(argc==1&&
(img=cvLoadImage("C:/psb.jpg",1))!=0)
{
IplImage*imag;
imag=cvCreateImage(cvSize(img->width,img->height),img->depth,1);
cvCvtColor(img,imag,CV_BGR2GRAY);
if(imag->nChannels!=1)
{
printf("error\n");
system("pause");
exit(0);
}
MidFilter(imag,9);
cvNamedWindow("中D值¦Ì滤?波¡§",1);
cvShowImage("中D值¦Ì滤?波¡§",imag);
cvWaitKey(0);
cvReleaseImage(&imag);
cvDestroyWindow("中D值¦Ì滤?波¡§");
system("pause");
return 0;
}
return -1;
}
IplImage*MidFilter(IplImage*image,int nwindow)
{
unsigned char* pwindowdata = newunsigned char[nwindow*nwindow];
memset(pwindowdata,0,nwindow*nwindow*sizeof(char));//数ºy组Á¨¦进?行D清?零¢?操¨´作Á¡Â.
unsigned char* ptemp = pwindowdata;
intnsize=(nwindow-1)/2;
for(int i=nsize;i<image->height-nsize;i++)
{
for(int j=nsize;j<image->width-nsize;j++)
{
ptemp = pwindowdata;
for(int m=-nsize;m<=nsize;m++)
{
for(int n=-nsize;n<=nsize;n++)
{
*ptemp = (unsigned char)(image->imageData[image->widthStep*(i+m)+(j+n)]);
ptemp++;
}
}
image->imageData[image->widthStep*i+j]= medvalue(pwindowdata,nwindow*nwindow);
}
}
return image;
}
unsigned char medvalue(unsigned char* array,int nlength)
{
unsignedchar* ptemp = array;
unsignedchar ntemp = 0;
for (int i = 0;i < nlength;i++)
{
ptemp = array;
for (int j = 0;j < nlength-i;j++)
{
if(*ptemp> *(ptemp+1))
{
ntemp = *ptemp;
*ptemp = *(ptemp+1);
*(ptemp+1) = ntemp;
ptemp++;
}
else
ptemp++;
}
}
return *(array+(nlength-1)/2);
}
数字图像基本处理算法
xizero00
常熟理工学院(CIT)计算机科学与工程学院 下一代互联网实验室(NGIL Lab)
Email:xizero00@163.com
由于SIFT算法需要用到很多算法,所以这段时间研究了一下一些最基本的图像处理算法,
好了,废话不多说,先看结果,接下来上图说话:
1.二值化:
图1 二值化(阈值:140)处理效果
所谓二值化简单一点讲,就是将图像划分成黑和白,通过设定一个标准如果大于这个标准就设为白,如果小于这个标准,就设为黑,而这个标准,就叫做阈值。
具体定义如下所示:
下面给出实现的代码:
//二值化 //函数的参数iTR为阈值 void CBMPSampleDlg::ThresholdProcess(int iTR) { //读取BMP文件 m_Dib.AttachMapFile("1.bmp", TRUE); m_Dib.CopyToMapFile("二值化.bmp");
//将像素指针定位到图像数据的开始 RGBTRIPLE *rgbtri = (RGBTRIPLE *)m_Dib.m_lpImage; //获得图像的大小 int iSize = m_Dib.GetSizeImage(); //BMP文件头指针 BITMAPINFOHEADER * pBmiHeader = (BITMAPINFOHEADER *)m_Dib.m_lpBMIH; //遍历每一个像素,并判断每一个像素的分量(RGB),将其与阈值比较,然后进行赋值 for(int i = 0; i < iSize/( pBmiHeader->biBitCount / 8); i++) { if ( (rgbtri[i].rgbtRed < iTR )| (rgbtri[i].rgbtGreen < iTR) | (rgbtri[i].rgbtBlue < iTR) ) { rgbtri[i].rgbtRed = (BYTE) 0; rgbtri[i].rgbtGreen = (BYTE) 0; rgbtri[i].rgbtBlue = (BYTE) 0; } else { rgbtri[i].rgbtRed = (BYTE) 255; rgbtri[i].rgbtGreen = (BYTE) 255; rgbtri[i].rgbtBlue = (BYTE) 255;
} } //显示图像 DrawPic(); }
|
代码中,首先会读取原始图像文件,文件的格式为BMP的,关于BMP图像的存储结构,在接下来的文章中会讲到。
在读取图像之后,会将指针定位到图像像素数据的开始位置,然后获得图像的大小,然后通过BMP文件头获得图像的一个像素所占据的二进制的位数,这样就知道一个像素由几个字节组成的了,需要注意的是,一个像素不一定是由三个字节组成的,比如是灰度图像其只需要一个字节来存储一个像素究竟是灰到什么程度其范围在0-255 之间,而彩色图像却是由三种颜色组成的也就是所说的三原色RGB分别为Red、Green、Blue三种颜色组成,这三种颜色每个分量各占一个字节,所以这里需要三个字节,另外在BMP图像中还一个结构为RGBQUAD的结构体,这里一个像素占据的是4个字节,其实,这里就涉及到了8位图像24位图像以及32位图像的问题了,所谓的8位图像其实,每一个像素占一个字节,24位图像,每一个像素占据3个字节、而32位图像每一个像素占据4个字节就是这么来的。
2.海报化
图2 海报化处理效果
所谓的海报化其实就是将每一个像素的分量与224进行与运算,而244的16进制表示可以表示成0xe0,前面介绍了一个像素的分量的范围在0-255范围内,所以只需要将这两个数值的二进制位相与即可完成海报化的处理效果。
下面为实现的具体代码:
//海报化 void CBMPSampleDlg::Posterize() { m_Dib.AttachMapFile("1.bmp", TRUE); m_Dib.CopyToMapFile("海报化.bmp"); RGBTRIPLE *rgbtri = (RGBTRIPLE *)m_Dib.m_lpImage; int iSize = m_Dib.GetSizeImage(); BITMAPINFOHEADER * pBmiHeader = (BITMAPINFOHEADER *)m_Dib.m_lpBMIH; for(int i = 0; i < iSize/( pBmiHeader->biBitCount / 8); i++) { rgbtri[i].rgbtRed = (BYTE) (rgbtri[i].rgbtRed & 0xe0); rgbtri[i].rgbtGreen = (BYTE) (rgbtri[i].rgbtGreen & 0xe0); rgbtri[i].rgbtBlue = (BYTE) (rgbtri[i].rgbtBlue & 0xe0); } DrawPic(); } |
上面的这段代码是我参考DirectShow里面的ezrgb24滤镜这个例子改写的,另外下面的灰度化也是采用里面的改写的。
3.灰度化
图3 灰度化处理效果
灰度化有很多种处理方法,有分量法、最大值法、平均值法以及加权平均值法。
1)分量法
将彩色图像中的三分量的亮度作为三个灰度图像的灰度值,可根据应用需要选取一种灰度图像。
f1(i,j)=R(i,j)f2(i,j)=G(i,j)f3(i,j)=B(i,j)
其中fk(i,j)(k=1,2,3)为转换后的灰度图像在(i,j)处的灰度值。
2)最大值法
将彩色图像中的三分量亮度的最大值作为灰度图的灰度值。
f(i,j)=max(R(i,j),G(i,j),B(i,j))
3) 平均值法
将彩色图像中的三分量亮度求平均得到一个灰度图。
f(i,j)=(R(i,j)+G(i,j)+B(i,j))/3
4) 加权平均法
根据重要性及其它指标,将三个分量以不同的权值进行加权平均。由于人眼对绿色的敏感最高,对蓝色敏感最低,因此,按下式对RGB三分量进行加权平均能得到较合理的灰度图像。
f(i,j)=0.30R(i,j)+0.59G(i,j)+0.11B(i,j))
在我们的程序中,我们采用的是加权平均法进行灰度化。
下面为实现的代码:
//灰度化 void CBMPSampleDlg::ConvertToGray() { m_Dib.AttachMapFile("1.bmp", TRUE); m_Dib.CopyToMapFile("灰度化.bmp"); RGBTRIPLE *rgbtri = (RGBTRIPLE *)m_Dib.m_lpImage; int iSize = m_Dib.GetSizeImage(); BITMAPINFOHEADER * pBmiHeader = (BITMAPINFOHEADER *)m_Dib.m_lpBMIH; int iGrayvalue = 0; //遍历每一个像素 for(int i = 0; i < iSize/( pBmiHeader->biBitCount / 8); i++) { iGrayvalue = int( rgbtri[i].rgbtBlue * 0.11 + rgbtri[i].rgbtGreen * 0.59 + rgbtri[i].rgbtRed * 0.3 ); rgbtri[i].rgbtRed = (BYTE) iGrayvalue; rgbtri[i].rgbtGreen = (BYTE) iGrayvalue; rgbtri[i].rgbtBlue = (BYTE) iGrayvalue; } DrawPic();
} |
在上述代码中,通过遍历每一个像素,然后计算该像素的三个分量的加权平均值,将三个分量设置成同一个值,这样就实现了对图像的灰度化处理。
4.模糊化
图4 模糊化处理效果
其实所谓的模糊化,就是将各个像素的相邻的像素的各个分量的值相加,然后除以2就可以实现对图像的模糊处理。
下面给出代码:
//模糊化 void CBMPSampleDlg::Blur() { m_Dib.AttachMapFile("1.bmp", TRUE); m_Dib.CopyToMapFile("模糊化.bmp"); RGBTRIPLE *rgbtri = (RGBTRIPLE *)m_Dib.m_lpImage; int iSize = m_Dib.GetSizeImage(); BITMAPINFOHEADER * pBmiHeader = (BITMAPINFOHEADER *)m_Dib.m_lpBMIH; LONG lHeight = pBmiHeader->biHeight; LONG lWidth = pBmiHeader->biWidth; for (int y = 0 ; y < lHeight; y++) { for (int x = 2 ; x < lWidth; x++, rgbtri ++) { rgbtri->rgbtRed = (BYTE) ((rgbtri->rgbtRed + rgbtri[2].rgbtRed) >> 1); rgbtri->rgbtGreen = (BYTE) ((rgbtri->rgbtGreen + rgbtri[2].rgbtGreen) >> 1); rgbtri->rgbtBlue = (BYTE) ((rgbtri->rgbtBlue + rgbtri[2].rgbtBlue) >> 1); } rgbtri +=2; } DrawPic(); } |
上面的代码同样是遍历每一个像素将前一个像素和后一个像素相加,然后将获得的值右移一位,这样就能实现除以2的效果,之所以做位运算,是因为位运算的速度比除法运算要快很多。
在介绍缩放算法之前,先介绍一些基本的概念
图像放大算法
图像放大有许多算法,其关键在于对未知像素使用何种插值方式。以下我们将具体分析几种常见的算法,然后从放大后的图像是否存在色彩失真,图像的细节是否得到较好的保存,放大过程所需时间是否分配合理等多方面来比较它们的优劣。
当把一个小图像放大的时候,比如放大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个临近点
好了,在介绍完了这些基础知识后,我们接下来讲解如何实现这些算法。
5.最临近点插值缩放
图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则为缩小。
6.双线性插值缩放
图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"); } |
7.高斯模糊
图12高斯模糊处理效果
这里的高斯平滑(模糊)或者说滤波器就是这样一种带权的平均滤波器. 那么这些权重如何分布呢? 我们先来看几个经典的模板例子:
尝试了使用这些滤波器对我们原来的图进行操作, 得到了这样的一组结果:
图13 原图
图14 3x3 高斯模板处理效果
图15 5x5 高斯模板处理效果
单纯从效果来看,两个模板都起到了平滑的作用, 只是程度有深浅的区分. 那么从理论上来说为什么能起到平滑的作用呢? 很显然,像素的颜色不仅由自身决定了, 同时有其周围的像素加权决定, 客观上减小了和周围像素的差异. 同时这些权重的设定满足了越近权重越大的规律. 从理论来讲, 这些权重的分布满足了著名的所谓高斯分布:
这就是1维的计算公式
这就是2维的计算公式
x, y表示的就是当前点到对应点的距离, 而那些具体的模板就是由这里公式中的一些特例计算而来. 需要说明的是不只有这么一些特例, 从wikipedia可以方便地找到那些复杂的模板。
下面给出实现的代码:
void CBMPSampleDlg::GaussianSmooth() { CDib srcDib; srcDib.AttachMapFile("1.bmp", TRUE); srcDib.CopyToMapFile("高斯模糊.bmp");
//获取旧的高和宽 LONG lOldHeight = srcDib.m_lpBMIH->biHeight; LONG lOldWidth = srcDib.m_lpBMIH->biWidth;
//保存旧的图像的字节 WORD wOldBitCount = srcDib.m_lpBMIH->biBitCount;
//获取旧图像每行字节数 LONG lOldLineBytes = WIDTHBYTES( lOldWidth * wOldBitCount );
PRGBTRIPLE * pOldRow = (PRGBTRIPLE *)malloc( sizeof(PRGBTRIPLE) * lOldHeight );
LPRGBTRIPLE SRgb = ( LPRGBTRIPLE ) srcDib.m_lpImage; //计算每一行的起点指针 for (int row = 0 ; row < lOldHeight ; row ++) { pOldRow[row] = PRGBTRIPLE ( (PBYTE)SRgb + row * lOldLineBytes ); }
//高斯模板 5 * 5 int templates[25] = { 1, 4, 7, 4, 1, 4, 16, 26, 16, 4, 7, 26, 41, 26, 7, 4, 16, 26, 16, 4, 1, 4, 7, 4, 1 };
//各个分量的和 int Bsum = 0 , GSum = 0 , RSum = 0;
//在高斯模板中的索引 int index = 0;
//用到的临时指针 PRGBTRIPLE PTemp = NULL , PDst = NULL;
//在模板中的值 int s = 0;
//计算在5*5的高斯模板中的目标像素RGB的值 for ( int row =2 ; row <lOldHeight - 2 ; row++ ) { for ( int col =2 ; col < lOldWidth - 2 ; col++ ) { Bsum = GSum = RSum = 0; index = 0;
for ( int m = row - 2 ; m < row + 3 ; m++) { for (int n=col - 2 ; n < col + 3 ; n++) { PTemp = pOldRow[m] + n; s = templates[index ++];
//Blue Bsum += PTemp->rgbtBlue * s;
//Green GSum += PTemp->rgbtGreen * s;
//Red RSum += PTemp->rgbtRed * s;
} }
Bsum /= 273; GSum /= 273; RSum /= 273;
//判断计算出来的值是否合法 //如果超过了255则设成255 //依次对BGR进行检查 if (Bsum > 255) { Bsum = 255; }
if (GSum > 255) { GSum = 255; }
if (RSum > 255) { RSum = 255; }
PDst = pOldRow[row] + col; PDst->rgbtBlue = Bsum; PDst->rgbtGreen = GSum; PDst->rgbtRed = RSum; } }
DrawPic();
} |
代码中给出的是5*5的高斯模板实现的高斯模糊。由于时间原因,就先粗略地讲到这里。