/********************************************************/
/* 图像空间几何变换:图像平移 ID_JHBH_PY(几何变换-平移)
/* 使用平移对话框:CImagePYDlg dlg
/* 算法:f(x,y)=f(x+x0,y+y0)图像所有点平移,空的补黑'0'
/* 注意该图像平移方法只是从左上角(0,0)处开始平移
/* 其他方向原理相同 自己去实现
/********************************************************/
void CImageProcessingView::OnJhbhPy()
{
if(numPicture==0) {
AfxMessageBox("载入图片后才能空间平移!",MB_OK,0);
return;
}
//定义采样对话框也是用来空间变换平移的坐标
CImagePYDlg dlg;
if( dlg.DoModal()==IDOK ) //显示对话框
{
//采样坐标最初为图片的自身像素
if( dlg.m_xPY>m_nWidth || dlg.m_yPY>m_nHeight ) {
AfxMessageBox("图片平移不能为超过原图长宽!",MB_OK,0);
return;
}
AfxMessageBox("图片空间变换-平移!",MB_OK,0);
//打开临时的图片 读写文件
FILE *fpo = fopen(BmpName,"rb");
FILE *fpw = fopen(BmpNameLin,"wb+");
fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);
fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);
fwrite(&bfh,sizeof(BITMAPFILEHEADER),1,fpw);
fwrite(&bih,sizeof(BITMAPINFOHEADER),1,fpw);
fread(m_pImage,m_nImage,1,fpo);
/************************************************************/
/* 图片空间变换-平移
/* 坐标(dlg.m_xPY,dlg.m_yPY)表示图像平移的坐标
/* 先用Plave计算出平移后的起始坐标,其他的坐标赋值为'0'黑色
/* 然后依次平移坐标,空的赋为黑色,否则填充
/************************************************************/
/******************************************************************/
/* 严重错误1:数组变量赋值相等
/* 在View.h中定义变量 BYTE *m_pImage 读入图片数据后的指针
/* 建立临时变量数组,让它平移变换 unsigned char *ImageSize
/* ImageSize=m_pImage(错误)
/* 会导致ImageSize赋值变换时m_pImage也产生了变换,所以输出全为黑色
/* 因为它俩指向了相同的数组地址
/* 解决方法:使用下面C++的new方法动态分配或for循环i=m_nImage赋值
/******************************************************************/
/*临时变量存储的像素与m_pImage相同,便于处理图像*/
unsigned char *ImageSize;
ImageSize=new unsigned char[m_nImage]; //new和delete有效的进行动态内存的分配和释放
int Place; //建立临时坐标 记录起始坐标(0,0)平移过来的位置
int m_pImagePlace; //原始图像平移为(0,0) 图像把它平移到Place位置
unsigned char black; //填充黑色='0'
/************************************************************/
/* for(int i=0 ; i=Place && countWidth=Place && countWidth>=dlg.m_xPY*3)
{
ImageSize[i]=m_pImage[m_pImagePlace];
m_pImagePlace++;
countWidth++;
if(countWidth==m_nWidth*3)
{
number++;
m_pImagePlace=number*m_nWidth*3;
}
}
}
fwrite(ImageSize,m_nImage,1,fpw);
fclose(fpo);
fclose(fpw);
numPicture = 2;
level=200; //200表示几何变换
Invalidate();
}
}
同时在ShowBitmap中添加level标记重新绘制图片,代码如下:
else //图像几何变换
if(level=200)
{
m_hBitmapChange = (HBITMAP) LoadImage(NULL,BmpNameLin,IMAGE_BITMAP,0,0,
LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION);
}
运行时需要注意一点:BMP图像在处理过程中可能会出现一些斜线,而平移(40,60)位移量时可能出现如下。他是因为BMP格式有个非常重要的规定,要求每一扫描的字节数据必须能被4整除,也就是Dword对齐(长度4字节),如果图像的一行字节数不能被4整除,就需要在每行末尾不起0达到标准。void CImageProcessingView::OnJhbhPy()
{
if(numPicture==0) {
AfxMessageBox("载入图片后才能空间平移!",MB_OK,0);
return;
}
//定义采样对话框也是用来空间变换平移的坐标
CImagePYDlg dlg;
if( dlg.DoModal()==IDOK ) //显示对话框
{
//采样坐标最初为图片的自身像素
if( dlg.m_xPY>m_nWidth || dlg.m_yPY>m_nHeight ) {
AfxMessageBox("图片平移不能为超过原图长宽!",MB_OK,0);
return;
}
AfxMessageBox("图片空间变换-平移!",MB_OK,0);
//打开临时的图片 读写文件
FILE *fpo = fopen(BmpName,"rb");
FILE *fpw = fopen(BmpNameLin,"wb+");
fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);
fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);
int num; //记录每行多余的图像素数个数
int sfSize; //补齐后的图像大小
//重点:图像的每行像素都必须是4的倍数:1*1的图像为 r g b 00H
if(m_nWidth*3%4!=0)
{
num=(4-m_nWidth*3%4);
sfSize=(m_nWidth*3+num)*m_nHeight; //每行多number个
}
else
{
num=0;
sfSize=m_nWidth*m_nHeight*3;
}
//注意:假如最后一行像素不足,我默认处理为完整的一行,不足补00H
//总之处理后的图像总是m*n且为4倍数,每行都完整存在
/*更改文件头信息 定义临时文件头结构变量*/
BITMAPFILEHEADER bfhsf;
BITMAPINFOHEADER bihsf;
bfhsf=bfh;
bihsf=bih;
bfhsf.bfSize=sfSize+54;
fwrite(&bfhsf,sizeof(BITMAPFILEHEADER),1,fpw);
fwrite(&bihsf,sizeof(BITMAPINFOHEADER),1,fpw);
fread(m_pImage,m_nImage,1,fpo);
CString str;
str.Format("补齐=%d",num);
AfxMessageBox(str);
/*临时变量存储的像素与sfSize相同 new和delete有效的进行动态内存的分配和释放*/
unsigned char *ImageSize;
ImageSize=new unsigned char[sfSize];
int Place; //建立临时坐标 记录起始坐标(0,0)平移过来的位置
int m_pImagePlace; //原始图像平移为(0,0) 图像把它平移到Place位置
unsigned char black=0; //填充黑色='0'
unsigned char other=0; //补码00H='\0'
Place=dlg.m_yPY*(m_nWidth*3+num); //前m_yPY行都要填充为黑色
m_pImagePlace=0; //图像处事位置为(0,0),把该点像素平移过去
int countWidth=0; //记录每行的像素个数,满行时变回0
int number=0; //数字记录使用的像素行数,平移时使用
for(int i=0 ; i=Place && countWidth=Place && countWidth>=dlg.m_xPY*3)
{
ImageSize[i]=m_pImage[m_pImagePlace];
m_pImagePlace++;
countWidth++;
if(countWidth==m_nWidth*3)
{
if(num==0)
{
countWidth=0;
number++;
m_pImagePlace=number*m_nWidth*3;
}
else //num为补0
{
for(int j=0;j
运行效果如下图所示,完美平移,其他算法遇到斜线问题类似补齐即可。
1.水平镜像翻转
其变换矩阵如下:
X=width-X0-1 (width为图像宽度)
Y=Y0
打开类向导,在CImageProcessingView中添加IDs为ID_JHBH_FZ,生成函数,代码如下:
/* 几何变换 图像翻转:自己对这个功能比较感兴趣,做个图像反转 */
void CImageProcessingView::OnJhbhFz()
{
if(numPicture==0) {
AfxMessageBox("载入图片后才能空间反转!",MB_OK,0);
return;
}
AfxMessageBox("图片空间变换-反转图像!",MB_OK,0);
//打开临时的图片
FILE *fpo = fopen(BmpName,"rb");
FILE *fpw = fopen(BmpNameLin,"wb+");
fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);
fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);
fwrite(&bfh,sizeof(BITMAPFILEHEADER),1,fpw);
fwrite(&bih,sizeof(BITMAPINFOHEADER),1,fpw);
fread(m_pImage,m_nImage,1,fpo);
/*new和delete有效的进行动态内存的分配和释放*/
unsigned char *ImageSize;
ImageSize=new unsigned char[m_nImage];
int countWidth=0; //记录每行的像素个数,满行时变回0
int Place; //记录图像每行的位置,便于图像反转
int number=0; //数字记录使用的像素行数
Place=m_nWidth*3-1;
//翻转矩阵: y=y0 x=width-x0-1
for(int i=0 ; i
运行效果如下图所示,其中还是存在一些小BUG,如前面的BMP图补0凑齐4整数倍宽度或颜色失帧。
/* 几何变换 图像倒转 */
void CImageProcessingView::OnJhbhDz()
{
if(numPicture==0) {
AfxMessageBox("载入图片后才能空间反转!",MB_OK,0);
return;
}
AfxMessageBox("图片空间变换-反转图像!",MB_OK,0);
//打开临时的图片
FILE *fpo = fopen(BmpName,"rb");
FILE *fpw = fopen(BmpNameLin,"wb+");
fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);
fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);
fwrite(&bfh,sizeof(BITMAPFILEHEADER),1,fpw);
fwrite(&bih,sizeof(BITMAPINFOHEADER),1,fpw);
fread(m_pImage,m_nImage,1,fpo);
/*new和delete有效的进行动态内存的分配和释放*/
unsigned char *ImageSize;
ImageSize=new unsigned char[m_nImage];
int countWidth=0; //记录每行像素个数,满行时变回0
int Place; //每列位置
int number=0; //像素行数
Place=(m_nWidth*3)*(m_nHeight-1-1); //0行存储
//翻转矩阵: x=x0 y=height-y0-1
for(int i=0 ; i
运行结果如下图所示,第二张图颜色没有失帧或变灰,这完全可以怀疑在翻转过程中RGB像素编程BGR后导致的结果,最终实现了翻转图像,但灰度存在一定;所以如果改为RBG顺序不变化即可原图颜色显示。
图像饶原点旋转顺时针theta角矩阵变换如下:注意BMP图像(0,0)左下角
//旋转图片
private void TurnPicture() {
Matrix matrix = new Matrix();
turnRotate=turnRotate+15;
//选择角度 饶(0,0)点选择 正数顺时针 负数逆时针 中心旋转
matrix.setRotate(turnRotate,bmp.getWidth()/2,bmp.getHeight()/2);
Bitmap createBmp = Bitmap.createBitmap(bmp.getWidth(), bmp.getHeight(), bmp.getConfig());
Canvas canvas = new Canvas(createBmp);
Paint paint = new Paint();
canvas.drawBitmap(bmp, matrix, paint);
imageCreate.setBackgroundColor(Color.RED);
imageCreate.setImageBitmap(createBmp);
textview2.setVisibility(View.VISIBLE);
}
实现效果如下图所示:
/**********************************************************/
/* 几何变换:图片旋转
/* 先添加对话框:IDD_JHBH_TXXZ(图像旋转),创建新类CImageXZDlg
/* 创建输入度数的:m_xzds Member variables 为int 0-360间
/**********************************************************/
void CImageProcessingView::OnJhbhTxxz()
{
if(numPicture==0) {
AfxMessageBox("载入图片后才能空间旋转!",MB_OK,0);
return;
}
//定义对话框并调用对话框
CImageXZDlg dlg;
if( dlg.DoModal()==IDOK ) //显示对话框
{
AfxMessageBox("图片空间变换-旋转图像!",MB_OK,0);
//读写文件
FILE *fpo = fopen(BmpName,"rb");
FILE *fpw = fopen(BmpNameLin,"wb+");
fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);
fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);
fwrite(&bfh,sizeof(BITMAPFILEHEADER),1,fpw);
fwrite(&bih,sizeof(BITMAPINFOHEADER),1,fpw);
fread(m_pImage,m_nImage,1,fpo);
/*new和delete有效的进行动态内存的分配和释放*/
unsigned char *ImageSize;
ImageSize=new unsigned char[m_nImage];
int Place; //记录图像每行的位置,便于图像旋转
/*定义PA=3.14时使用的方法是arcsin(1.0/2)*6即为π*/
double PA;
PA=asin(0.5)*6;
/*把输入的0-360的正整数度数转换为角度,30度=π/6*/
double degree;
degree=PA*dlg.m_xzds/180; //调用dlg.m_xzds(旋转度数)
//对应的二维矩阵 注意图像矩阵从左下角开始处理 它最终要转换成一维存储
int X,Y; //图像变换前通过一维矩阵转换为二维
int XPlace,YPlace;
//输出转换为的角度
CString str;
str.Format("转换后的角度=%f",degree);
AfxMessageBox(str);
//图像旋转处理
for(int i=0 ; i=0 && XPlace<=m_nWidth) && (YPlace>=0 && YPlace<=m_nHeight) )
{
Place=YPlace*m_nWidth*3+XPlace*3;
//在图像范围内赋值为该像素
if(Place+2
运行效果如下图所示,中心旋转太难了!找到中心那个位置就不太容易,我做不下去了,fuck~同时旋转过程中,由于是饶左下角(0,0)实现,故有的角度会到界面外显示全黑。下图分别旋转15度和355度。 图像缩放主要有两种方法:
1.最近邻插值:向后映射时,输出图像的灰度等于离它所映射位置最近的输入图像的灰度值。其中向前映射和向后映射如下:
对于向前映射每个输出图像的灰度要经过多次运算,对于向后映射,每个输出图像的灰度只经过一次运算。在实际应用中,更多的是采用向后映射法,其中根据四个相邻像素灰度值计算某个位置的像素灰度值即为灰度级插值。
2.双线性插值:四点确定一个平面函数,属于过约束问题。即单位正方形顶点已知,求正方形内任一点的f(x,y)值。
/*******************************************************************/
/* ID_JHBH_SF: 几何运算-缩放-最近邻插值算法
/* 算法思想:输出图像的灰度等于离它所映射位置最近的输入图像的灰度值
/* 先计算出放大缩小后的长宽,根据它计算找原图中的点灰度,四舍五入
/*******************************************************************/
void CImageProcessingView::OnJhbhSf()
{
if(numPicture==0) {
AfxMessageBox("载入图片后才能几何缩放图像!",MB_OK,0);
return;
}
CImageSFDlg dlg; //定义缩放对话框
if( dlg.DoModal()==IDOK )
{
//采样坐标最初为图片的自身像素 m_sfbs(缩放倍数)
if( dlg.m_sfbs==0 ) {
AfxMessageBox("输入图片缩放倍数不能为0!",MB_OK,0);
return;
}
FILE *fpo = fopen(BmpName,"rb");
FILE *fpw = fopen(BmpNameLin,"wb+");
fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);
fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);
/*先求缩放后的长宽*/
int sfWidth,sfHeight; //缩放后的长宽
int sfSize; //缩放后的图像大小
sfWidth=(int)(m_nWidth*(dlg.m_sfbs*1.0)/100); //24位图像RGB必须是3倍数 循环读取时为RGB
sfHeight=(int)(m_nHeight*(dlg.m_sfbs*1.0)/100);
int number; //记录每行多余的图像素数个数
//重点:图像的每行像素都必须是4的倍数:1*1的图像为 r g b 00H
if(sfWidth*3%4!=0) {
number=(4-sfWidth*3%4);
sfSize=(sfWidth*3+(4-sfWidth*3%4))*sfHeight;
}
else {
number=0;
sfSize=sfWidth*sfHeight*3;
}
//注意:假如最后一行像素不足,我默认处理为完整的一行,不足补00H
//总之处理后的图像总是m*n且为4倍数,每行都完整存在
/*更改文件头信息 定义临时文件头结构变量*/
BITMAPFILEHEADER bfhsf;
BITMAPINFOHEADER bihsf; //缩放(sf)
bfhsf=bfh;
bihsf=bih;
bfhsf.bfSize=sfSize+54;
bihsf.biWidth=sfWidth;
bihsf.biHeight=sfHeight;
//显示部分m_nDrawWidth<650显示原图,否则显示
flagSF=1; //图像缩放为1标识变量
m_nDrawWidthSF=sfWidth;
m_nDrawHeightSF=sfHeight;
fwrite(&bfhsf,sizeof(BITMAPFILEHEADER),1,fpw);
fwrite(&bihsf,sizeof(BITMAPINFOHEADER),1,fpw);
fread(m_pImage,m_nImage,1,fpo);
unsigned char red,green,blue;
unsigned char other=0; //补码00H='\0'
int placeX; //记录在原图中的第几行的位置
int placeY; //记录在原图中的位置(x,y)
int placeBH; //记录变换后在变换图中的位置
/*new和delete有效的进行动态内存的分配和释放*/
unsigned char *ImageSize;
ImageSize=new unsigned char[sfSize];
/*读取文件像素信息 缩放注意:1.找最近灰度 2.四舍五入法(算法+0.5)*/
for(int i=0; i
第四步:因为图像缩放修改BMP图片头信息,所以需要修改ShowBitmap中的显示第二张图片时的部分代码。如下所示:添加变量flagSF、m_nDrawWidthSF和m_nDrawHeightSF。
/*定义显示图像缩放时的长宽与标记*/
int flagSF=0; //图像几何变换缩放变换
int m_nDrawWidthSF=0; //图像显示宽度缩放后
int m_nDrawHeightSF=0; //图像显示高度缩放后
//****************显示BMP格式图片****************//
void CImageProcessingView::ShowBitmap(CDC *pDC, CString BmpName)
{
......
else //图像几何变换
if(level=200)
{
m_hBitmapChange = (HBITMAP) LoadImage(NULL,BmpNameLin,IMAGE_BITMAP,0,0,
LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION);
}
if( m_bitmap.m_hObject ) {
m_bitmap.Detach(); //m_bitmap为创建的位图对象
}
m_bitmap.Attach(m_hBitmapChange);
//定义并创建一个内存设备环境
CDC dcBmp;
if( !dcBmp.CreateCompatibleDC(pDC) ) //创建兼容性的DC
return;
BITMAP m_bmp; //临时bmp图片变量
m_bitmap.GetBitmap(&m_bmp); //将图片载入位图中
CBitmap *pbmpOld = NULL;
dcBmp.SelectObject(&m_bitmap); //将位图选入临时内存设备环境
//图片显示调用函数StretchBlt
if(flagSF==1)
{
CString str;
str.Format("缩放长=%d 宽%d 原图长=%d 宽=%d",m_nDrawWidthSF,
m_nDrawHeightSF,m_nWidth,m_nHeight);
AfxMessageBox(str);
flagSF=0;
//m_nDrawWidthSF缩放此存见函数最近邻插值法中赋值
if(m_nDrawWidthSF<650 && m_nDrawHeightSF<650)
pDC->StretchBlt(m_nWindowWidth-m_nDrawWidthSF,0,
m_nDrawWidthSF,m_nDrawHeightSF,&dcBmp,0,0,m_bmp.bmWidth,m_bmp.bmHeight,SRCCOPY);
else
pDC->StretchBlt(m_nWindowWidth-640,0,640,640,&dcBmp,0,0,
m_bmp.bmWidth,m_bmp.bmHeight,SRCCOPY); //显示大小为640*640
}
else {
//如果图片太大显示大小为固定640*640 否则显示原图大小
if(m_nDrawWidth<650 && m_nDrawHeight<650)
pDC->StretchBlt(m_nWindowWidth-m_nDrawWidth,0,
m_nDrawWidth,m_nDrawHeight,&dcBmp,0,0,m_bmp.bmWidth,m_bmp.bmHeight,SRCCOPY);
else
pDC->StretchBlt(m_nWindowWidth-640,0,640,640,&dcBmp,0,0,
m_bmp.bmWidth,m_bmp.bmHeight,SRCCOPY);
}
//恢复临时DC的位图
dcBmp.SelectObject(pbmpOld);
}
运行效果如下图所示,采用最近邻插值法缩放大了会出现失帧。