图像旋转 imgrotation
根据输入的角度进行旋转变换
很简单,套公式。
遍历输出的图像的像素,去原图中找对应像素,超出边界置0
如果遍历原图像素,去构建输出图像像素则会有很多噪声,因为浮点数计算的误差
数据校验及数据转换
void CBMPDlg::OnRota() { // TODO: Add your control notification handler code here CDC *pDC = m_image1.GetDC(); CRect myRECT; m_image1.GetClientRect(&myRECT); //数据校验 GetDlgItem(IDC_ANGLE)->GetWindowText(m_angle); if(m_angle.GetLength()==0) { MessageBox("Input Angle First~ ","Ou no~",MB_OK); return; } else { int cnt_point=0; for(int i=0;i<m_angle.GetLength();i++) { char c=m_angle.GetAt(i); if(c>='0'&&c<='9'||c=='.') { if(c=='.')cnt_point++; if(cnt_point<=1)continue; } MessageBox("wrong input~ ","wrong~",MB_OK); return; } } if(hasopened==false) { MessageBox("Open the file~ ","I miss it~",MB_OK); return; } double angle=atof((LPCSTR(m_angle)));//CString to double imgrotation im(filepath); im.setAngle(angle); //检查角度 CString str; str.Format("Rotation Angle:%.2lf",im.getAngle()); SetDlgItemText(IDC_OUTPUT,str); im.rotation(pDC,myRECT); m_image1.ReleaseDC(pDC); }
旋转公式图解:
void imgrotation::rotation(CDC *pDC, CRect rc) { angle/=57.3; int ox=vImageWidth()/2; int oy=vImageHeight()/2; img_r=(unsigned char*)malloc(sizeof(unsigned char)*vPixelBytes()); unsigned int _img_w = vImageWidth()*3; if(_img_w%4!=0)_img_w=(_img_w/4+1)*4; memcpy(img_r,raw_img,sizeof(unsigned char)*vPixelBytes()); int x,y,i,j; for(y=0;y<vImageHeight();y++) { for(x=0;x<vImageWidth();x++) { int xx=(x-ox)*cos(angle)-(y-oy)*sin(angle)+ox; int yy=(x-ox)*sin(angle)+(y-oy)*cos(angle)+oy; if(xx>=0&&xx<vImageWidth()&&yy>=0&&yy<vImageHeight()) { raw_img[y*_img_w+x*3] =img_r[yy*_img_w+xx*3]; //B raw_img[y*_img_w+x*3+1]=img_r[yy*_img_w+xx*3+1]; //G raw_img[y*_img_w+x*3+2]=img_r[yy*_img_w+xx*3+2]; //R } else { raw_img[y*_img_w+x*3] =0; //B raw_img[y*_img_w+x*3+1]=0; //G raw_img[y*_img_w+x*3+2]=0 ; //R } } } imshow2(pDC, rc); }
效果图:
再来看张图
锯齿非常严重,放大后简直不忍直视:
双线性插值
为了解决以上问题,我用了双线性插值算法
void imgrotation::rotation2(CDC *pDC, CRect rc) { //使用raw_img,遍历输出图像在原图中找对应像素,超出部分置0 angle/=57.3; int ox=vImageWidth()/2; int oy=vImageHeight()/2; //img_r=(unsigned char*)malloc(sizeof(unsigned char)*vPixelBytes()); unsigned int _img_w = vImageWidth()*3; if(_img_w%4!=0)_img_w=(_img_w/4+1)*4; memcpy(img_r,raw_img,sizeof(unsigned char)*vPixelBytes()); int i,j,k; double fcos=cos(angle); double fsin=sin(angle); for(i=0;i<vImageHeight();i++) { for(j=0;j<vImageWidth();j++) { //双线性法时x,y一定要用double型,否则下面的权重u,v将不起作用 double x=(j-ox)*fcos-(i-oy)*fsin+ox; double y=(j-ox)*fsin+(i-oy)*fcos+oy; if(x<0 || x>=vImageWidth() || y<0 || y>=vImageHeight()) { //设置背景色 for(k=0;k<3;k++) { raw_img[i*_img_w+j*3+k]=0; } continue; } int x1,x2,y1,y2;//距离当前点最近的4个点坐标 x1=(int)x; x2=x1+1; y1=(int)y; y2=y1+1; //设置双线性法权值 double u,v; u=x-x1; v=y-y1; //处理边界问题 if(x2>=vImageWidth())u=0; if(y2>=vImageHeight())v=0; unsigned int Q11,Q21,Q12,Q22,R1,R2,P; for(k=0;k<3;k++) { Q11=img_r[y1*_img_w+x1*3+k]; Q21=img_r[y1*_img_w+x2*3+k]; if(y2<vImageHeight()){//防止计算越界 Q12=img_r[y2*_img_w+x1*3+k]; Q22=img_r[y2*_img_w+x2*3+k]; } R1=(1-u)*Q11+u*Q21; R2=(1-u)*Q12+u*Q22; P=(1-v)*R1+v*R2; raw_img[i*_img_w+j*3+k]=P; } } } imshow2(pDC, rc); }
效果对比图:
还有一个明显的问题:无法显示所有像素,那么就要重新输出图像的宽高,还要重新计算偏移量:
void imgrotation::rotation(CDC *pDC, CRect rc) { //使用imgbasic中的img数据,outHeight,outWidth为输出图像高宽 angle/=57.3; double fcos=cos(angle); double fsin=sin(angle); //计算图片实际的高和宽 int srcx1,srcx2,srcx3,srcx4; int srcy1,srcy2,srcy3,srcy4; srcx1 = 0; srcy1 = 0; srcx2 = 0; srcy2 = vImageWidth()-1; srcx3 = vImageHeight()-1; srcy3 = 0; srcx4 = vImageHeight()-1; srcy4 = vImageWidth()-1; double tranx1,tranx2,tranx3,tranx4; double trany1,trany2,trany3,trany4; tranx1=srcy1*fcos-srcx1*fsin; trany1=srcy1*fsin+srcx1*fcos; tranx2=srcy2*fcos-srcx2*fsin; trany2=srcy2*fsin+srcx2*fcos; tranx3=srcy3*fcos-srcx3*fsin; trany3=srcy3*fsin+srcx3*fcos; tranx4=srcy4*fcos-srcx4*fsin; trany4=srcy4*fsin+srcx4*fcos; //计算输出图像的高和宽 unsigned int outHeight,outWidth,pixelByte; outHeight=(unsigned int)(max(fabs(trany4-trany1),fabs(trany3-trany2))+1.5); outWidth=(unsigned int)(max(fabs(tranx4-tranx1),fabs(tranx3-tranx2))+1.5); unsigned int _imgr_w = outWidth*3; //img_r=(unsigned char*)malloc(sizeof(unsigned char)*outHeight*outWidth*3); double dx=-0.5*outWidth*fcos-0.5*outHeight*fsin+0.5*vImageWidth(); double dy=0.5*outWidth*fsin-0.5*outHeight*fcos+0.5*vImageHeight(); unsigned int _raw_w=vImageWidth()*3; int i,j,k; for(i=0;i<outHeight;i++) { for(j=0;j<outWidth;j++) { double x=j*fcos+i*fsin+dx; double y=-j*fsin+i*fcos+dy; if(x<0 || x>=vImageWidth() || y<0 || y>=vImageHeight()) { //设置背景色 for(k=0;k<3;k++) { img_r[i*_imgr_w+j*3+k]=200; } continue; } int x1,x2,y1,y2;//距离当前点最近的4个点坐标 x1=(int)x; x2=x1+1; y1=(int)y; y2=y1+1; //设置双线性法权值 double u,v; u=x-x1; v=y-y1; //处理边界问题 if(x2>=vImageWidth())u=0; if(y2>=vImageHeight())v=0; unsigned int Q11,Q21,Q12,Q22,R1,R2,P; for(k=0;k<3;k++) { Q11=img[y1*_raw_w+x1*3+k]; Q21=img[y1*_raw_w+x2*3+k]; if(y2<vImageHeight()){//防止计算越界 Q12=img[y2*_raw_w+x1*3+k]; Q22=img[y2*_raw_w+x2*3+k]; } R1=(1-u)*Q11+u*Q21; R2=(1-u)*Q12+u*Q22; P=(1-v)*R1+v*R2; img_r[i*_imgr_w+j*3+k]=P; // img_r[i*_imgr_w+j*3+k]=img[y1*_raw_w+x1*3+k]; 双线性处理前后对比 } } } //暂时无法实现快速显示,用慢速的 for(i=0;i<outHeight;i++){ for(j=0;j<outWidth;j++){ unsigned char R,G,B; R = img_r[i*_imgr_w+j*3+0]; G = img_r[i*_imgr_w+j*3+1]; B = img_r[i*_imgr_w+j*3+2]; pDC->SetPixel((rc.Width()-outWidth)/2+j,(rc.Height()-outHeight)/2+i,RGB(R,G,B)); } } }
旋转85度的效果图:(边缘的锯齿还有待改善