有 library (如 OpenCV) 可直接調用。

沒 library 建議至少要有 matrix library,自己刻很累。

demo 圖片故意挑 bpp 24, no compression , height = 200 , width = 169

順便把 width alignment 問題解決掉。主要做以下測試

 

1. 轉置 transpose

2. 平移 offset

3. 縮放 scaling

4. 鏡像 mirror

5. 斜變 shear

6. 2d 旋轉 rotation : 繞原點、繞 (a,b)、resize 

7. 3d 旋轉投影

 

程式碼優化不是本文重點,為表達清晰,犧牲效能 <效能非常差>。 

桶狀變型概念簡單,要有一點線代底,有機會再講。 

 

通用變數與資料型態

 

byte : unsigned char

h, height : 原始影像高度

w, width : 原始影像寬度

nh : 新影像高度

nw : 新影像寬度

x, y : 原始影像之點 P(y, x)

nx, ny : 新影像之點 P(ny,nx)

byte r[h][w], g[h][w], b[h][w] : 原始影像之 rgb

byte nr[h][w], ng[h][w], nb[h][w] : 新影像之 rgb

 

說明用變數

x0 , y0 : 原始影像位置 (y0,, x0)

x1 , y1 : 新影像位置 (y1,, x1)

 

轉置, Transport

 

nh = width

nw = height

nr[ny][nx] = r[x][y]

 

Code Snippet
  1. void bmp_trans(
  2.     byte** nr, byte** ng, byte **nb, /* 新影像 rgb */
  3.     byte** r, byte** g, byte** b,    /* 舊影像 rgb */
  4.     int height, int width            /* 原始長寬   */
  5.     )
  6. {
  7.     int x,y;
  8.     for(y=0; y<height; ++y){
  9.         for(x=0; x<width; ++x){
  10.             nr[x][y] = r[y][x];
  11.             nb[x][y] = b[y][x];
  12.             ng[x][y] = g[y][x];
  13.         }
  14.     }
  15. }

 

transpose  

 

平移 (a, b) , Shift

nx = x0 + a

ny = y0 + b

可寫成逆運算型式

x0 = x1 - a

y0 = y1 - b

Code Snippet
  1. void bmp_shift(
  2.     byte** nr, byte** ng, byte **nb,  /* 新影像 rgb */
  3.     byte** r, byte** g, byte** b,     /* 舊影像 rgb */
  4.     int height, int width,            /* 原始長寬   */
  5.     int ox, int oy)                   /* 位移大小   */
  6. {
  7.     int y, x, nx,ny;
  8.     for(ny=0; ny<height; ++ny){
  9.         for(nx=0; nx<width; ++nx){
  10.             //nx=x+ox, ny=y+oy;
  11.             x=nx-ox, y=ny-oy;
  12.             if(x<width && y<height && x>=0 && y>=0) {
  13.                 nr[ny][nx] = r[y][x];
  14.                 ng[ny][nx] = g[y][x];
  15.                 nb[ny][nx] = b[y][x];
  16.             } else {
  17.                 nr[ny][nx]=ng[ny][nx]=nb[ny][nx]=0;
  18.             }
  19.         }
  20.     }
  21. }

可原地進行,但需判斷 a, b 之正負號,決定複製之方向性 (由頭到尾或由尾到頭)。

 

 shift  

 

縮放 (a, b) 倍, Scaling

 

x1 = a*x0

y1 = b*y0

Code Snippet
  1. void bmp_scaling_1(
  2.     byte** nr, byte** ng, byte **nb,   /* 新影像 rgb */
  3.     byte** r, byte** g, byte** b,      /* 舊影像 rgb */
  4.     int height, int width,             /* 原始長寬   */
  5.     double sa,double sb)               /* 縮放倍率   */
  6. {
  7.     int nh = (int)(height * sa + 0.5); // 四捨五入
  8.     int nw = (int)(width  * sb + 0.5); // 四捨五入
  9.     int x,y ,x1, y1;
  10.     memset(*nr,0,nh*nw);
  11.     memset(*ng,0,nh*nw);
  12.     memset(*nb,0,nh*nw);
  13.     for(y=0; y<height; ++y){
  14.         for(x=0; x<width; ++x){
  15.             y1 = (int)(y*sa+0.5), x1=(int)(x*sb+0.5);
  16.             nr[y1][x1] = r[y][x];
  17.             ng[y1][x1] = g[y][x];
  18.             nb[y1][x1] = b[y][x];
  19.         }
  20.     }
  21. }

 

scaling_1  

 

上面那格線不是我事後畫的。用三個 memset 較差,不過不是這裡重點。部份情況將造成新影像格線現象,以逆運算方式為佳。

x0 = x1 / a;

y0 = y1 / b;

Code Snippet
  1. void bmp_scaling_2(
  2.     byte** nr, byte** ng, byte **nb,   /* 新影像 rgb */
  3.     byte** r, byte** g, byte** b,      /* 舊影像 rgb */
  4.     int height, int width,             /* 原始長寬   */
  5.     double sa,double sb)               /* 縮放倍率   */
  6. {
  7.     int nh = (int)(height * sa + 0.5); // 四捨五入
  8.     int nw = (int)(width  * sb + 0.5); // 四捨五入
  9.     int x,y ,x1, y1;
  10.     for(y1=0; y1<nh; ++y1){
  11.         for(x1=0; x1<nw; ++x1){
  12.             y=(int)(y1/sa+0.5), x=(int)(x1/sb+0.5);
  13.             nr[y1][x1] = r[y][x];
  14.             ng[y1][x1] = g[y][x];
  15.             nb[y1][x1] = b[y][x];
  16.         }
  17.     }
  18. }

 

scaling_2  

 

可原地進行,但必須先判別 scale > 1 或 scale <1 才可決定複製方向性,內插法修正影像資料為佳。

 

 

左右鏡像, Mirror

 

x1 = Width - x0 - 1

y1 = y0  

Code Snippet
  1. void bmp_mirror_1(
  2.     byte** nr, byte** ng, byte **nb, /* 新影像 rgb */
  3.     byte** r, byte** g, byte** b,    /* 舊影像 rgb */
  4.     int height, int width)           /* 原始長寬   */
  5. {
  6.     int x, y;
  7.     for(y=0; y<height; ++y)
  8.         for(x=0; x<width; ++x){
  9.             nr[y][x]=r[y][width-x-1];
  10.             ng[y][x]=g[y][width-x-1];
  11.             nb[y][x]=b[y][width-x-1];
  12.         }
  13. }

 

mirror_1  

 

利用 swap 可原地進行。

 

上下鏡像, Mirror


x1 = x0

y1 = Height - y0 - 1


Code Snippet
  1. void bmp_mirror_2(
  2.     byte** nr, byte** ng, byte **nb, /* 新影像 rgb */
  3.     byte** r, byte** g, byte** b,    /* 舊影像 rgb */
  4.     int height, int width)           /* 原始長寬   */
  5. {
  6.     int x, y;
  7.     for(y=0; y<height; ++y)
  8.         for(x=0; x<width; ++x){
  9.             nr[y][x]=r[height-y-1][x];
  10.             ng[y][x]=g[height-y-1][x];
  11.             nb[y][x]=b[height-y-1][x];
  12.         }
  13. }


mirror_2  


利用 swap 可原地進行。

斜變, shear / skew

 

< 長方形變平行四邊形 >  令傾斜率為 r 

x1 = x0 + r * y0 

y1 = y0

 

Code Snippet
  1. void bmp_shear_1(
  2.     byte** nr, byte** ng, byte **nb, /* 新影像 rgb */
  3.     byte** r, byte** g, byte** b,    /* 舊影像 rgb */
  4.     int height, int width,           /* 原始長寬   */
  5.     double sr)                       /* 斜變率     */
  6. {
  7.     int x, y, nx, ny;
  8.     int nh = (int)(height+sr*width+0.5);
  9.     int nw = width;
  10.     for(y=0; y<height; ++y){
  11.         for(x=0; x<width; ++x){
  12.             ny = (int)(y+sr*x+0.5), nx=x;
  13.             nr[ny][nx] = r[x][y];
  14.             ng[ny][nx] = g[x][y];
  15.             nb[ny][nx] = b[x][y];
  16.         }
  17.     }
  18. }

 

shear_1   

 

x1 = x0

y1 = r * x0 + y0

 

Code Snippet
  1. void bmp_shear_2(
  2.     byte** nr, byte** ng, byte **nb, /* 新影像 rgb */
  3.     byte** r, byte** g, byte** b,    /* 舊影像 rgb */
  4.     int height, int width,           /* 原始長寬   */
  5.     double sr)                       /* 斜變率     */
  6. {
  7.     int x, y, nx, ny;
  8.     int nh = height;
  9.     int nw = (int)(width+sr*height+0.5);
  10.     for(y=0; y<height; ++y){
  11.         for(x=0; x<width; ++x){
  12.             ny=y, nx = (int)(x+sr*y+0.5);
  13.             nr[ny][nx] = r[x][y];
  14.             ng[ny][nx] = g[x][y];
  15.             nb[ny][nx] = b[x][y];
  16.         }
  17.     }
  18. }

 

shear_2   

 

   可原地進行,需判別 r 之範圍。以內插法修補影像為佳。

 

2d 逆時針旋轉 theta 角 - 繞原點 , 2d Rotation

 

畫圖,用 sin(a+b) = sina cosb + cosa sinb 等和角公式可推出。

 

de_rot_1  

 

x1 = x0 * cos(theta) - y0 * sin(theta)

y1 = x0 * sin(theta) + y0 * cos(theta)

 

順時可將 theta 改成 -theta,故 sin(-theta) = - sin(theta) , cos(-theta) = cos(theta),帶回上式即可得,不再贅述。

Code Snippet
  1. void bmp_rot_1(
  2.     byte** nr, byte** ng, byte **nb, /* 新影像 rgb */
  3.     byte** r, byte** g, byte** b,    /* 舊影像 rgb */
  4.     int height, int width,           /* 原始長寬   */
  5.     double theta)                    /* 旋轉角度   */
  6. {
  7.     int x, y, nx, ny;
  8.     double vcos, vsin;
  9.  
  10.     theta *= 0.01745329252; // 轉弳度
  11.     vsin = sin(theta), vcos = cos(theta);
  12.     
  13.     for(y=0; y<height; ++y)    for(x=0; x<width; ++x)
  14.         nr[y][x]=ng[y][x]=nb[y][x]=0;
  15.     for(y=0; y<height; ++y){
  16.         for(x=0; x<width; ++x){
  17.             nx=(int)(vcos*x - vsin*y+0.5);
  18.             ny=(int)(vsin*x + vcos*y+0.5);
  19.             if(ny>=0 && ny<height && nx>=0 && nx<width){
  20.                 nr[ny][nx]=r[y][x];
  21.                 ng[ny][nx]=g[y][x];
  22.                 nb[ny][nx]=b[y][x];
  23.             }
  24.         }
  25.     }
  26. }

 

rotation_1  

 

上述會有「空洞現象」,即不是每個 (x1, y1) 都會對應到 (x0, y0) 故要求 inverse rotation matrix ,一樣畫圖及和角公式可推得。

 

de_rot_2   

 

x0 =  x1 * cos(theta) + y1 * sin(theta)

y0 = -x1 * sin (theta) + y1 * cos(theta)


Code Snippet
  1. void bmp_rot_2(
  2.     byte** nr, byte** ng, byte **nb, /* 新影像 rgb */
  3.     byte** r, byte** g, byte** b,    /* 舊影像 rgb */
  4.     int height, int width,           /* 原始長寬   */
  5.     double theta)                    /* 旋轉角度   */
  6. {
  7.     int x, y, nx, ny;
  8.     double vcos, vsin;
  9.  
  10.     theta *= 0.01745329252; // 轉弳度
  11.     vsin = sin(theta), vcos = cos(theta);
  12.  
  13.     for(ny=0; ny<height; ++ny){
  14.         for(nx=0; nx<width; ++nx){
  15.             x = (int)( nx * vcos + ny * vsin + 0.5);
  16.             y = (int)(-nx * vsin + ny * vcos + 0.5);
  17.             if(y>=0 && y<height && x>=0 && x<width){
  18.                 nr[ny][nx]=r[y][x];
  19.                 ng[ny][nx]=g[y][x];
  20.                 nb[ny][nx]=b[y][x];
  21.             } else {
  22.                 nr[ny][nx]=ng[ny][nx]=nb[ny][nx]=0;
  23.             }
  24.         }
  25.     }
  26. }

 

 rotation_2  

2d 逆時針旋轉 theta 角- 繞 (ox,oy) , 2d Rotation

 

上述之 Rotation 是以原點為中心進行旋轉,若欲以 (ox,oy) 為中心進行旋轉時,

正向運算不推了,對反運算而言,掌控幾個技巧

 

(1) nx , ny 先平移 (-ox,-oy),得到 nx2, ny2 

    nx2 = nx - ox , ny2 = ny - oy

 

(2) nx2, ny2 經由旋轉矩陣,旋轉 theta 度,得到對應的 x, y

 

    x = nx2 * cos(theta) + ny2 * sin(theta) + ox

(3) 再將 x, y 平移 (ox, oy)

    y = -nx2 * sin(theta) + ny2 * cos(theta) + oy

 

Code Snippet
  1. void bmp_rot_3(
  2.     byte** nr, byte** ng, byte **nb, /* 新影像 rgb */
  3.     byte** r, byte** g, byte** b,    /* 舊影像 rgb */
  4.     int height, int width,           /* 原始長寬   */
  5.     double theta)                    /* 旋轉角度   */
  6. {
  7.     int x, y, nx, ny;
  8.     double vcos, vsin;
  9.     // 假設對中心進行旋轉
  10.     int ox = (width-1)/2;
  11.     int oy = (height-1)/2;    
  12.     int nx2, ny2; //平移後之點
  13.  
  14.     theta *= 0.01745329252; // 轉弳度
  15.     vsin = sin(theta), vcos = cos(theta);
  16.  
  17.     for(ny=0; ny<height; ++ny){
  18.         for(nx=0; nx<width; ++nx){
  19.             // 平移 ox,oy
  20.             nx2 = nx - ox, ny2 = ny - oy;
  21.             // 再旋轉, 平移(-ox,-oy)
  22.             x = (int)( nx2 * vcos + ny2 * vsin + 0.5 + ox);
  23.             y = (int)(-nx2 * vsin + ny2 * vcos + 0.5 + oy);
  24.             // 寫入
  25.             if(y>=0 && y<height && x>=0 && x<width){
  26.                 nr[ny][nx]=r[y][x];
  27.                 ng[ny][nx]=g[y][x];
  28.                 nb[ny][nx]=b[y][x];
  29.             } else {
  30.                 nr[ny][nx]=ng[ny][nx]=nb[ny][nx]=0;
  31.             }
  32.         }
  33.     }
  34. }

 

rotation_3   

 

2d 逆時針旋轉 theta 角- resize , 2d Rotation

 

de_rot_size  

 

新的 size 計算不難,這裡用的是無腦的方式。把原本的四個角先計算得到旋轉後的座標,再找 max_x, max_y, min_x, min_y,新的寬度和長度做相減可得到 ( 會有些許誤差 )

 

Code Snippet
  1. #define MAX(a,b) ( (a) > (b) ? (a) : (b))
  2. #define MIN(a,b) ( (a) < (b) ? (a) : (b))
  3. void bmp_rot_size(
  4.     int *n_height, int *n_width,   /* 新影像之寬高 */
  5.     int height, int width,         /* 原影像之寬高 */
  6.     double theta)                  /* 旋轉角度     */
  7. {    
  8.     double x1, y1, x2, y2;
  9.     double x3, y3, x4, y4;
  10.     double fx1, fx2, fx3, fx4;
  11.     double fy1, fy2, fy3, fy4;
  12.  
  13.     double vcos, vsin;
  14.     double max_x, max_y, min_x, min_y;
  15.  
  16.     theta *= 0.01745329252; // 轉弳度
  17.     vcos = cos(theta), vsin = sin(theta);
  18.  
  19.     // 原本四個角點
  20.     x1=0, y1=0;
  21.     x2=width-1, y2=0;
  22.     x3=width-1, y3=height-1;
  23.     x4=0, y4=height-1;
  24.  
  25.     // 旋轉後四個角點
  26.     // nx=(int)(vcos*x - vsin*y+0.5);
  27.     fx1 = vcos*x1 - vsin*y1;
  28.     fx2 = vcos*x2 - vsin*y2;
  29.     fx3 = vcos*x3 - vsin*y3;
  30.     fx4 = vcos*x4 - vsin*y4;
  31.     // ny=(int)(vsin*x + vcos*y+0.5);
  32.     fy1 = vsin*x1 + vcos*y1;
  33.     fy2 = vsin*x2 + vcos*y2;
  34.     fy3 = vsin*x3 + vcos*y3;
  35.     fy4 = vsin*x4 + vcos*y4;
  36.     // find max_x, max_y, min_x, min_y
  37.     max_x = MAX( fx1, MAX(fx2, MAX(fx3, fx4)));
  38.     min_x = MIN( fx1, MIN(fx2, MIN(fx3, fx4)));
  39.     max_y = MAX( fy1, MAX(fy2, MAX(fy3, fy4)));
  40.     min_y = MIN( fy1, MIN(fy2, MIN(fy3, fy4)));
  41.  
  42.     *n_width = (int)(max_x-min_x+0.5);
  43.     *n_height= (int)(max_y-min_y+0.5);
  44. }

做完 resize 要做 rotation 時,無論對哪個點做旋轉都一樣的,因最後一定會被平移,使得最左下角的座標為 (0,0)。這裡便不再推導,直接看程式碼。

Code Snippet
  1. void bmp_rot_4(
  2.     byte** nr, byte** ng, byte **nb, /* 新影像 rgb   */
  3.     byte** r, byte** g, byte** b,    /* 舊影像 rgb   */
  4.     int n_height, int n_width,       /* 新影像長寬   */
  5.     int height, int width,           /* 原始長寬     */
  6.     double theta)                    /* 旋轉角度     */
  7. {
  8.     
  9.     int x, y, nx, ny;
  10.     double vcos, vsin;
  11.     double h2, w2;   // 舊影像長寬之一半
  12.     double nh2, nw2; // 新影像長寬之一半
  13.     double ox, oy;   // offset
  14.  
  15.     h2 =0.5*(height-1), w2=0.5*(width-1);
  16.     nh2=0.5*(n_height-1), nw2=0.5*(n_width-1);
  17.     
  18.     theta *= 0.01745329252; // 轉弳度
  19.     vsin = sin(theta), vcos = cos(theta);
  20.  
  21.     // 算 offset
  22.     ox = -nw2 * vcos - nh2 * vsin + w2;
  23.     oy =  nw2 * vsin - nh2 * vcos + h2;
  24.     for(ny=0; ny<n_height; ++ny){
  25.         for(nx=0; nx<n_width; ++nx){
  26.             x = (int)( nx * vcos + ny * vsin + 0.5 + ox);
  27.             y = (int)(-nx * vsin + ny * vcos + 0.5 + oy);
  28.             // 寫入
  29.             if(y>=0 && y<height && x>=0 && x<width){
  30.                 nr[ny][nx]=r[y][x];
  31.                 ng[ny][nx]=g[y][x];
  32.                 nb[ny][nx]=b[y][x];
  33.             } else {
  34.                 nr[ny][nx]=ng[ny][nx]=nb[ny][nx]=0;
  35.             }
  36.         }
  37.     }
  38. }

 

 rotation_4  

 

 3d 旋轉投影

 

看一下 3d 繞 y 軸旋轉公式。

 

x2 = x1 * cos(theta) + z1 * sin(theta)

y2 = y1

z2 = -x1 * sin(theta) + z1 * cos(thea)

 

投影到 xy 平面時,z 分量代 0 進去,得到

 

x2 = x1 * cos(theta)

y2 = y1

 

直接用這組公式代入時必須注意,它是「繞 y 軸旋轉」,而不是繞「圖片本身中垂線」旋轉,所以當 cos(theta) < 0 的時候 (也就是 90<= theta <= 270 ),這張圖會被轉到原本可視範圍之外。

一種改善的方式是直接判斷 cos(theta) < 0 是否成立,若成立的話 x2 計算時再加上 width 當 offset 做修正。這裡提另一想法。

令  cos(theta)   =  r ,原本公式可改寫如下

x2 = 0
y2 = y1 , if r =0

x2 = x1 * r
y2 = y1 ,  if r > 0

x2 = -x1 * r + width = width - (x1 * r)  
y2 = y1, if r < 0

看起來便清楚多了。在 r > 0 的情況下,實際上影像是進行 (r, 1) 之 scaling;在 r < 0 之情形下,先做 (r, 1) 之 scaling 後,再進行左右之 mirror。所以欲達到自身旋轉的效果時,只需要將 scaling 後之結果進行 x 軸之 offset 即可。實做細節較繁雜,此處不再示例。

edisonx 發表在 痞客邦 PIXNET 留言(0) 人氣()