DIP-HW4

实验四

一、实验目的和要求

  • 目的
    • 了解图像变换的种类
    • 掌握常见几何变换(平移、旋转、对称、投影、放缩)的方法。
    • 了解插值的原因、目的及原理。
    • 掌握线性插值、双线性插值的方法,并比较其效果。
  • 要求
    • 自己写c语言程序,实现所有操作,不得调用库。

二、实验内容和原理

  • 内容: 对图像进行几何变换。

    • 读入24位彩图
    • 依次对图像进行平移、旋转、对称、投影和放缩变换
    • 输出变换后的图像
  • 原理:

    ​ 对图像进行几何变换操作的原理是,对于即将生成的新图像中每个像素点$P’=(x’,y’)$ ,我们利用图像几何变换的逆变换求得其在原始图像中对应的点 $P=(x,y)=T(x’,y’)$ 。则$R(x’,y’)=R(x,y);G(x’,y’)=G(x,y);B(x’,y’)=B(x,y);$

    ​ 很多情况下我们得到的 $(x,y)$ 并不是整数,这个时候我们需要选择一个方法来得到$(x,y) $应当对应的$RGB$值。这个时候就用到插值法。即利用$(x,y)$ 附近的一些像素点的$RGB$值来计算得到$(x’,y’)$的$RGB$值。

    • 平移 $(dx,dy)$

      $$ \left{ \begin{aligned} x = x’-dx\y = y’-dy \end{aligned} \right. $$

    • 逆时针旋转 $\theta$

      一些常量的定义:

      $pw,ph (原图的宽度和高度)$

      $nw,nh (新图的宽度和高度)$

      $cx=pw/2 (原图中心点的横坐标)$

      $cy=ph/2 (原图中心点的横坐标)$

      $dx=(nw-pw)/2$

      $dy=(nh-ph)/2$

      定义两个中间变量:

      $a=x’-dx-cx+cxcos\theta-cysin\theta$

      $b=y’-dy-cy+cycos\theta+cxsin\theta$

      $$ \left{ \begin{aligned} x = bsin\theta +acos\theta \y = bcos\theta-asin\theta \end{aligned} \right. $$

    • **投影 $\theta$ **

      x轴方向 $$ \begin{cases} x = x’ -y’*tan\theta \y =y’ \end{cases} $$

      y轴方向 $$ \begin{cases} x = x’ \y =y’ -x’*tan\theta \end{cases} $$

    • 对称

      x轴方向 $$ \begin{cases} x = pw -x’-1 \y =y’ \end{cases} $$

      y轴方向 $$ \begin{cases} x = x’ \y =ph-y’-1 \end{cases} $$

    • 放缩,比例为$(a,b)$

      $$ \begin{cases} x = \frac{x’}{a} \y =\frac{y’}{b} \end{cases} $$

    • 插值法

      一些常量的定义:

      $x,y \rightarrow (根据变换公式计算得到的坐标,可能为小数)$

      $x_1= \lfloor x \rfloor $

      $y_1= \lfloor y \rfloor $

      $x_2= \lceil x \rceil $

      $y_2= \lceil y \rceil $

      $radio_{11}=x-x_1$

      $radio_{12}=x_2-x$

      $radio_{21}=y-y1$

      $radio_{22}=y2-y$

      (理论上 $radio_{11}=\frac{x-x_1}{x_2-x_1}$ ,由于$x_2-x_1=1$ 。其他$radio$同理)

      $C\in {R,G,B} \rightarrow (原始图像的RGB值)$

      $C’\in {R’,G’,B’} \rightarrow (处理后图像的RGB值)$

      则:

      $$C’(x’,y’)=radio_{21}*(radio_{11}*C(x_1,y_1)+radio_{12}C(x_1,y_2))\\qquad\qquad+ radio_{22}(radio_{11}*C(x_2,y_1)+radio_{12}*C(x_2,y_2))$$

      上述公式即为双线性插值法。

      其本质在于,利用像素点变换前的实际位置周围四个像素点的 $RGB$ 值的加权平均来计算新像素点的 $RGB$ 值。并且如果周围的像素点距离实际位置越远,其的值对实际的 $RGB$ 值影响越小。原理如下图所示。

      image-20191116193730438

三、源代码和分析

  • 源代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    339
    340
    341
    342
    343
    344
    345
    346
    347
    348
    349
    350
    351
    352
    353
    354
    355
    356
    357
    358
    359
    360
    361
    362
    363
    364
    365
    366
    367
    368
    369
    370
    371
    372
    373
    374
    375
    376
    377
    378
    379
    380
    381
    382
    383
    384
    385
    386
    387
    388
    389
    390
    391
    392
    393
    394
    395
    396
    397
    398
    399
    400
    401
    402
    403
    404
    405
    406
    407
    408
    409
    410
    411
    412
    413
    414
    415
    416
    417
    418
    419
    420
    421
    422
    423
    424
    425
    426
    427
    428
    429
    430
    431
    432
    433
    434
    435
    436
    437
    438
    439
    440
    441
    442
    443
    444
    445
    446
    447
    448
    449
    450
    451
    452
    453
    454
    455
    456
    457
    458
    459
    460
    461
    462
    463
    464
    465
    466
    467
    468
    469
    470
    471
    472
    473
    474
    475
    476
    477
    478
    479
    480
    481
    482
    483
    484
    485
    486
    487
    488
    489
    490
    491
    492
    493
    494
    495
    496
    497
    498
    499
    500
    501
    502
    503
    504
    505
    506
    507
    508
    509
    510
    511
    512
    513
    514
    515
    516
    517
    518
    519
    520
    521
    522
    523
    524
    525
    526
    527
    528
    529
    /*
    本代码的坐标系统:
    左上角对应坐标原点,水平向右为x轴正向,垂直向下为y轴正向。
    根据bmp文件的扫描顺序,这样的坐标系统刚好符合常规的坐标系。
    */
    #include<stdio.h>
    #include<stdlib.h>
    #include<math.h>
    #include<string.h>
    #define WORD unsigned short
    #define DWORD unsigned int
    #define BYTE unsigned char
    #define INF 9999999
    const double pi=3.14;
    int ph,pw,psize,lsize,cx,cy;
    typedef struct BMP_FILE_HEADER
    {
    WORD bfType;
    DWORD bfSize;
    WORD bfReserved1;
    WORD bfReserved2;
    DWORD bfOffBits;
    }BMPFILEHEADER;
    /* 位图信息头 */
    typedef struct BMP_INFO
    {
    DWORD biSize; // 信息头的大小
    DWORD biWidth; // 图像的宽度
    DWORD biHeight; // 图像的高度
    WORD biPlanes; // 图像的位面数
    WORD biBitCount; // 每个像素的位数
    DWORD biCompression; // 压缩类型
    DWORD biSizeImage; // 图像的大小,以字节为单位
    DWORD biXPelsPerMeter; // 水平分辨率
    DWORD biYPelsPerMeter; // 垂直分辨率
    DWORD biClrUsed; // 使用的色彩数
    DWORD biClrImportant; // 重要的颜色数
    } BMPINF; // 40 字节
    BMPFILEHEADER thisBmpFileHeader; // 定义一个 BMP 文件头的结构体
    BMPINF thisBmpInfo; // 定义一个 BMP 文件信息结构体
    void readFileHead(FILE *fp); //fp指向的文件的文件头读入到thisBMPFileHeader中
    void writeFileHead(FILE *fp); //thisBMPFileHeader写入到fp指向的文件的文件头中
    void Translation(BYTE *p,int dx,int dy,int flag);//进行平移操作,dx,dy为坐标移动的增量。flag=0时,画布增大,flag=1时,画布不增大。
    void Rotation(BYTE *p,double theta);//对图像旋转theta角度
    void Rotation2(BYTE *p,double theta);//对图像旋转theta角度
    void Shear(BYTE *p,double theta,int flag);//对图像投影theta角度
    void Scale(BYTE *p,float a,float b);//对图像放缩变换,a,b分别表示横纵坐标方向的比例。
    void Mirror(BYTE *p,int flag);//对图像进行对称变换,flag=0时x轴,flag=1时y轴
    double GetRolX(int x,int y,double theta);
    double GetRolY(int x,int y,double theta);
    int GetRolHW(int *x,int *y,double theta);
    int Interpolation(BYTE *p,double x,double y,int _);
    void OutPutImg(BYTE *pOUT,int nw,int nh,char *name);
    int main()
    {
    FILE *fp; // 定义一个文件指针
    if((fp=fopen("in.bmp","rb"))==NULL)
    {
    printf("Can't open the file\n");
    return 0;
    }
    printf("open the file\n");
    readFileHead(fp);
    ph=thisBmpInfo.biHeight;//高度
    pw=thisBmpInfo.biWidth;//宽度
    psize=pw*ph*3;//图片信息字节总数
    lsize=pw*3;//每行信息字节总数
    BYTE* p=(BYTE*)malloc(psize);
    fread(p,psize,1,fp);
    fclose(fp);
    Translation(p,-200,-100,0);
    Translation(p,-200,-100,1);
    Rotation(p,pi/3.0);
    Rotation2(p,pi/3.0);
    Scale(p,1.2,0.8);
    Shear(p,pi/6,0);
    Shear(p,pi/6,1);
    Mirror(p,0);
    Mirror(p,1);
    return 0;
    }
    void readFileHead(FILE *fp)
    {
    fread(&thisBmpFileHeader.bfType,sizeof(thisBmpFileHeader.bfType),1,fp);
    fread(&thisBmpFileHeader.bfSize,sizeof(thisBmpFileHeader.bfSize),1,fp);
    fread(&thisBmpFileHeader.bfReserved1,sizeof(thisBmpFileHeader.bfReserved1),1,fp);
    fread(&thisBmpFileHeader.bfReserved2,sizeof(thisBmpFileHeader.bfReserved2),1,fp);
    fread(&thisBmpFileHeader.bfOffBits,sizeof(thisBmpFileHeader.bfOffBits),1,fp);
    fread(&thisBmpInfo.biSize, sizeof(thisBmpInfo.biSize), 1, fp);
    fread(&thisBmpInfo.biWidth, sizeof(thisBmpInfo.biWidth), 1, fp);
    fread(&thisBmpInfo.biHeight, sizeof(thisBmpInfo.biHeight), 1, fp);
    fread(&thisBmpInfo.biPlanes, sizeof(thisBmpInfo.biPlanes), 1, fp);
    fread(&thisBmpInfo.biBitCount, sizeof(thisBmpInfo.biBitCount), 1, fp);
    fread(&thisBmpInfo.biCompression, sizeof(thisBmpInfo.biCompression), 1, fp);
    fread(&thisBmpInfo.biSizeImage, sizeof(thisBmpInfo.biSizeImage), 1, fp);
    fread(&thisBmpInfo.biXPelsPerMeter, sizeof(thisBmpInfo.biXPelsPerMeter), 1, fp);
    fread(&thisBmpInfo.biYPelsPerMeter, sizeof(thisBmpInfo.biYPelsPerMeter), 1, fp);
    fread(&thisBmpInfo.biClrUsed, sizeof(thisBmpInfo.biClrUsed), 1, fp);
    fread(&thisBmpInfo.biClrImportant, sizeof(thisBmpInfo.biClrImportant), 1, fp);
    }
    void writeFileHead(FILE *fp)
    {
    // 写入位图文件头
    fwrite(&thisBmpFileHeader.bfType,sizeof(thisBmpFileHeader.bfType),1,fp);
    fwrite(&thisBmpFileHeader.bfSize,sizeof(thisBmpFileHeader.bfSize),1,fp);
    fwrite(&thisBmpFileHeader.bfReserved1,sizeof(thisBmpFileHeader.bfReserved1),1,fp);
    fwrite(&thisBmpFileHeader.bfReserved2,sizeof(thisBmpFileHeader.bfReserved2),1,fp);
    fwrite(&thisBmpFileHeader.bfOffBits,sizeof(thisBmpFileHeader.bfOffBits),1,fp);
    // 写入位图信息头
    fwrite(&thisBmpInfo.biSize, sizeof(thisBmpInfo.biSize), 1, fp);
    fwrite(&thisBmpInfo.biWidth, sizeof(thisBmpInfo.biWidth), 1, fp);
    fwrite(&thisBmpInfo.biHeight, sizeof(thisBmpInfo.biHeight), 1, fp);
    fwrite(&thisBmpInfo.biPlanes, sizeof(thisBmpInfo.biPlanes), 1, fp);
    fwrite(&thisBmpInfo.biBitCount, sizeof(thisBmpInfo.biBitCount), 1, fp);
    fwrite(&thisBmpInfo.biCompression, sizeof(thisBmpInfo.biCompression), 1, fp);
    fwrite(&thisBmpInfo.biSizeImage, sizeof(thisBmpInfo.biSizeImage), 1, fp);
    fwrite(&thisBmpInfo.biXPelsPerMeter, sizeof(thisBmpInfo.biXPelsPerMeter), 1, fp);
    fwrite(&thisBmpInfo.biYPelsPerMeter, sizeof(thisBmpInfo.biYPelsPerMeter), 1, fp);
    fwrite(&thisBmpInfo.biClrUsed, sizeof(thisBmpInfo.biClrUsed), 1, fp);
    fwrite(&thisBmpInfo.biClrImportant, sizeof(thisBmpInfo.biClrImportant), 1, fp);
    }
    void Translation(BYTE *p,int dx,int dy,int flag)
    {
    if(flag!=1&&flag!=0)
    {
    printf("Translation Error! Please input a right value of flag!\n");
    return;
    }
    if(dx%4) dx=(dx/4+1)*4;//保证x轴方向的偏移量为4的倍数
    int i,j,tmp,n;
    if(flag==1)
    {
    BYTE *pOUT=(BYTE*)malloc(psize);
    for(i=0;i<ph;i++)
    for(j=0;j<pw;j++)
    {
    tmp=lsize*i+j*3;
    int tmpi=i-dy,tmpj=j-dx;
    if(tmpi<0) tmpi+=ph;
    if(tmpi>=ph) tmpi-=ph;
    if(tmpj<0) tmpj+=pw;
    if(tmpj>=pw) tmpj-=pw;
    int Ntmp = tmpi*lsize+tmpj*3;
    pOUT[tmp]=p[Ntmp];
    pOUT[tmp+1]=p[Ntmp+1];
    pOUT[tmp+2]=p[Ntmp+2];
    }
    OutPutImg(pOUT,pw,ph,"Translation1");
    }
    else
    {
    int nh = ph+abs(dy),nw=pw+abs(dx),nsize=nw*nh*3;
    BYTE *pOUT=(BYTE*)malloc(nw*nh*3);
    int oi[2],oj[2];
    if(dx>=0)
    {
    oj[0]=dx;
    oj[1]=nw-1;
    }
    if(dx<0)
    {
    oj[0]=0;
    oj[1]=ph-1;
    }
    if(dy>=0)
    {
    oi[0]=dy;
    oi[1]=nh-1;
    }
    if(dy<0)
    {
    oi[0]=0;
    oi[1]=pw-1;
    }
    for(i=0;i<nh;i++)
    for(j=0;j<nw;j++)
    {
    int Ntmp = i*nw*3+j*3;
    if(i>=oi[0]&&i<=oi[1]&&j>=oj[0]&&j<=oj[1])
    {

    tmp=lsize*(i-oi[0])+(j-oj[0])*3;
    pOUT[Ntmp]=p[tmp];
    pOUT[Ntmp+1]=p[tmp+1];
    pOUT[Ntmp+2]=p[tmp+2];
    }
    else
    {
    pOUT[Ntmp]=pOUT[Ntmp+1]=pOUT[Ntmp+2]=0;
    }
    }
    OutPutImg(pOUT,nw,nh,"Translation0");
    }
    }
    void Rotation(BYTE *p,double theta)//对图像旋转theta角度
    {
    int nh,nw;
    GetRolHW(&nw,&nh,theta);
    BYTE *pOUT=(BYTE*)malloc(nh*nw*3);
    int dX=(nw-pw)/2,dY=(nh-ph)/2; // 由于图像放大,计算坐标后需要移动一定距离
    int i,j,tmp;
    for(i=0;i<nh;i++)
    for(j=0;j<nw;j++)
    {
    int _;
    tmp=i*nw*3+j*3;
    for(_=0;_<3;_++)
    pOUT[tmp+_]=0;
    }

    for(i=0;i<nh;i++)
    for(j=0;j<nw;j++)
    {
    tmp=nw*i*3+j*3;
    double a,b;
    a=j-dX-cx+cx*cos(theta)-cy*sin(theta);
    b=i-dY-cy+cy*cos(theta)+cx*sin(theta);
    double x,y;
    x=b*sin(theta)+a*cos(theta);
    y=b*cos(theta)-a*sin(theta);
    int _;
    for(_=0;_<3;_++)
    pOUT[tmp+_]=Interpolation(p,x,y,_);
    }
    int x=theta*180/pi;
    char s1[100]="Rotation",s[20];
    sprintf(s,"_%d_degree",x);
    strcat(s1,s);
    OutPutImg(pOUT,nw,nh,s1);
    }
    void Shear(BYTE *p,double theta,int flag)//对图像投影theta角度,flag==0 -> x轴投影,1->y轴
    {
    BYTE* pOUT;
    char s1[100]="Shear",s[20];
    int x=theta*180/pi;
    if(flag!=1&&flag!=0)
    {
    printf("Shear Error! Please input a right value of flag!\n");
    return;
    }
    int nw,nh,tmp,i,j,_;
    if(flag==0)
    {
    sprintf(s,"_X_%d_degree",x);
    nh=ph;
    nw=pw+ph*tan(theta);
    if(nw%4) nw=(nw/4+1)*4;
    pOUT=(BYTE*)malloc(nw*nh*3);
    for(i=0;i<nh;i++)
    for(j=0;j<nw;j++)
    for(_=0;_<3;_++)
    pOUT[i*nw*3+j*3+_]=0;
    for(i=0;i<nh;i++)
    for(j=0;j<nw;j++)
    {
    double x=(double)j-tan(theta)*i,
    y=(double)i;
    tmp=i*nw*3+j*3;
    int _;
    for(_=0;_<3;_++)
    pOUT[tmp+_]=Interpolation(p,x,y,_);
    }
    }
    else
    {
    sprintf(s,"_Y_%d_degree",x);
    nh=ph+pw*tan(theta);
    nw=pw;
    pOUT=(BYTE*)malloc(nw*nh*3);
    for(i=0;i<nh;i++)
    for(j=0;j<nw;j++)
    for(_=0;_<3;_++)
    pOUT[i*nw*3+j*3+_]=0;
    for(i=0;i<nh;i++)
    for(j=0;j<nw;j++)
    {
    double y=(double)i-tan(theta)*j,
    x=(double)j;
    tmp=i*nw*3+j*3;
    int _;
    for(_=0;_<3;_++)
    pOUT[tmp+_]=Interpolation(p,x,y,_);
    }
    }
    strcat(s1,s);
    OutPutImg(pOUT,nw,nh,s1);
    }
    void Scale(BYTE *p,float a,float b)//对图像放缩变换,a,b分别表示横纵坐标方向的比例。
    {
    int nh=(int)(ph*b),
    nw=(int)(pw*a);
    if(nw%4) nw=(nw/4+1)*4;
    BYTE *pOUT=(BYTE*)malloc(nh*nw*3);
    int *flag=(int *)malloc(sizeof(int)*nw*nh);
    memset(pOUT,0,nh*nw*3);
    memset(flag,0,sizeof(int)*nh*nw);
    int i,j,tmp;
    for(i=0;i<nh;i++)
    for(j=0;j<nw;j++)
    {
    tmp=i*nw*3+j*3;
    double x=floor((j*1.0)/a),
    y=floor((i*1.0)/b);
    int _;
    for(_=0;_<3;_++)
    pOUT[tmp+_]=Interpolation(p,x,y,_);
    }
    OutPutImg(pOUT,nw,nh,"Scale");
    }
    void Mirror(BYTE *p,int flag)//对图像进行对称变换,flag=0时x轴,flag=1时y轴
    {
    BYTE *pOUT=(BYTE*)malloc(psize);
    int i,j,tmp;
    if(flag==0)
    {
    for(i=0;i<ph;i++)
    for(j=0;j<pw;j++)
    {
    tmp=i*lsize+j*3;
    pOUT[tmp]=p[i*lsize+(pw-j-1)*3];
    pOUT[tmp+1]=p[i*lsize+(pw-j-1)*3+1];
    pOUT[tmp+2]=p[i*lsize+(pw-j-1)*3+2];
    }
    FILE *fp=fopen("MirrorX.bmp","wb");
    writeFileHead(fp);
    fwrite(pOUT,psize,1,fp);
    fclose(fp);
    printf("MirrorX finised!\n");
    }
    if(flag==1)
    {
    for(j=0;j<pw;j++)
    for(i=0;i<ph;i++)
    {
    tmp=i*lsize+j*3;
    pOUT[tmp]=p[(ph-i-1)*lsize+j*3];
    pOUT[tmp+1]=p[(ph-i-1)*lsize+j*3+1];
    pOUT[tmp+2]=p[(ph-i-1)*lsize+j*3+2];
    }
    FILE *fp=fopen("MirrorY.bmp","wb");
    writeFileHead(fp);
    fwrite(pOUT,psize,1,fp);
    fclose(fp);
    printf("MirrorY finised!\n");
    }
    }
    double GetRolX(int x,int y,double theta)
    {
    cx=pw/2;
    cy=ph/2;
    x-=cx;
    y-=cy;
    double RolX=x*cos(theta)-y*sin(theta);
    return RolX=RolX+cx;
    }
    double GetRolY(int x,int y,double theta)
    {
    cx=pw/2;
    cy=ph/2;
    x-=cx;
    y-=cy;
    double RolY=x*sin(theta)+y*cos(theta);
    return RolY=RolY+cy;
    }
    int GetRolHW(int *x,int *y,double theta)
    {
    double tmpx[4],tmpy[4],Maxx=-1,Minx=INF,Maxy=-1,Miny=INF;
    int fx[4]={0},fy[4]={0};
    fx[2]=fx[1]=pw;
    fy[2]=fy[3]=ph;
    int i;
    for(i=0;i<4;i++)
    {
    tmpx[i]=GetRolX(fx[i],fy[i],theta);
    tmpy[i]=GetRolY(fx[i],fy[i],theta);
    if(tmpx[i]>Maxx) Maxx=tmpx[i];
    if(tmpx[i]<Minx) Minx=tmpx[i];
    if(tmpy[i]>Maxy) Maxy=tmpy[i];
    if(tmpy[i]<Miny) Miny=tmpy[i];
    }
    *x=(int)(Maxx-Minx)+2;
    if((*x)%4) *x=(*x/4+1)*4;
    *y=(int)(Maxy-Miny)+2;
    }
    int Interpolation(BYTE *p,double x,double y,int _)
    {
    int x1,x2,y1,y2;
    x1=floor(x);y1=floor(y);
    x2=x1+1;y2=y1+1;
    if(x1>=0&&x1<pw-1&&y1>=0&&y1<ph-1)
    {
    float radio11=x-x1,radio12=x2-x,
    radio21=y-y1,radio22=y2-y;
    int tmp1,tmp2,tmp3,tmp4;
    tmp1=y1*lsize+x1*3;
    tmp2=y1*lsize+x2*3;
    tmp3=y2*lsize+x1*3;
    tmp4=y2*lsize+x2*3;
    int X1,X2;
    X1=p[tmp1+_]*radio11+p[tmp2+_]*radio12;
    X2=p[tmp3+_]*radio11+p[tmp4+_]*radio12;
    return X1*radio21+X2*radio22;
    }
    else
    return 0;
    }
    void OutPutImg(BYTE *pOUT,int nw,int nh,char *name)
    {
    char postfix[10]=".bmp",tmp[100];
    strcpy(tmp,name);
    strcat(tmp,postfix);
    FILE *fp=fopen(tmp,"wb");
    thisBmpFileHeader.bfSize=thisBmpFileHeader.bfSize-psize+nh*nw*3;
    thisBmpInfo.biWidth=nw;
    thisBmpInfo.biHeight=nh;
    thisBmpInfo.biSizeImage=nw*nh*3;
    writeFileHead(fp);
    fwrite(pOUT,nw*nh*3,1,fp);
    fclose(fp);
    printf("%s",name);
    printf(" finised!\n");
    thisBmpInfo.biWidth=pw;
    thisBmpInfo.biHeight=ph;
    thisBmpInfo.biSizeImage=pw*ph*3;
    }
    void Rotation2(BYTE *p,double theta)//对图像旋转theta角度
    {
    int nh,nw;
    GetRolHW(&nw,&nh,theta);
    int *fMinX=(int *)malloc(nh*sizeof(int)),
    *fMaxX=(int *)malloc(nh*sizeof(int)),//记录旋转后图像每一行最大、小的横坐标
    *fMinY=(int *)malloc(nw*sizeof(int)),
    *fMaxY=(int *)malloc(nw*sizeof(int));//记录旋转后图像每一列最大、小的纵坐标
    int *flag=(int *)malloc(nw*nh*sizeof(int));
    BYTE *pOUT=(BYTE*)malloc(nh*nw*3);
    int dX=(nw-pw)/2,dY=(nh-ph)/2; // 由于图像放大,计算坐标后需要移动一定距离
    int i,j,tmp;
    for(i=0;i<nh;i++)
    {
    fMinX[i]=INF;
    fMaxX[i]=-1;
    }
    for(i=0;i<nw;i++)
    {
    fMinY[i]=INF;
    fMaxY[i]=-1;
    }
    for(i=0;i<nh;i++)
    for(j=0;j<nw;j++)
    {
    flag[i*nw+j]=0;
    int _;
    tmp=i*nw*3+j*3;
    for(_=0;_<3;_++)
    pOUT[tmp+_]=0;
    }

    for(i=0;i<ph;i++)
    for(j=0;j<pw;j++)
    {

    tmp=lsize*i+j*3;
    int tx=(int)GetRolX(j,i,theta)+dX,
    ty=(int)GetRolY(j,i,theta)+dY,
    Ntmp=ty*nw*3+tx*3;
    pOUT[Ntmp]=p[tmp];
    pOUT[Ntmp+1]=p[tmp+1];
    pOUT[Ntmp+2]=p[tmp+2];
    if(tx<fMinX[ty]) fMinX[ty]=tx;
    if(tx>fMaxX[ty]) fMaxX[ty]=tx;
    if(ty<fMinY[tx]) fMinY[tx]=ty;
    if(ty>fMaxY[tx]) fMaxY[tx]=ty;

    flag[ty*nw+tx]=1;
    }
    for(i=0;i<nh;i++)
    {
    int lastj=-1;
    for(j=0;j<nw;j++)
    {
    tmp=i*nw*3+j*3;

    if(j<fMinX[i]||j>fMaxX[i])
    {
    int _=0;
    for(_=0;_<3;_++)
    pOUT[tmp+_]=0;
    continue;
    }
    if(flag[i*nw+j]==1)
    {
    lastj=j;
    continue;
    }
    else
    {
    int nextj=j;
    while(nextj<nw&&flag[i*nw+nextj]==0) nextj++;
    int _,tmpl=i*nw*3+lastj*3,tmpn=i*nw*3+nextj*3;
    if(nextj>=nw)
    {
    if(lastj==-1) break;
    else
    for(_=0;_<3;_++)
    pOUT[tmp+_]=pOUT[tmpl+_];

    }
    else
    {
    if(lastj==-1)
    for(_=0;_<3;_++)
    pOUT[tmp+_]=pOUT[tmpn+_];
    else
    {
    float radio1=(j-lastj)*1.0/(nextj-lastj),
    radio2=(nextj-j)*1.0/(nextj-lastj);
    for(_=0;_<3;_++)
    pOUT[tmp+_]=radio2*pOUT[tmpn+_]+radio1*pOUT[tmpl+_];
    }
    }
    }
    }
    }
    int x=theta*180/pi;
    char s1[100]="Rotation2",s[20];
    sprintf(s,"_%d_degree",x);
    strcat(s1,s);
    OutPutImg(pOUT,nw,nh,s1);
    }

  • 分析

    • 坐标系统和存储系统

      • 代码中像素点的颜色信息存储在 $P$ 数组中。$P$ 是个一维数组,我们可以把它排列成二维数组,通过计算可以获取下标。$P[tmp]\rightarrow P[i][j] ,(tmp=iwh+j*3)$

      • 代码中的坐标系统是把下标为$P[0][0]$ 的像素点作为坐标原点,每一行下标增长的方向作为 $X$ 轴的正方向。每一列下标增长的方向作为 $Y$ 轴的正方向。

        即 坐标为 $(i,j)$ 的像素点对应的下标为 $P[j][i]$

      • bmp图像的数据扫描顺序是,每一行从左向右扫描,而行则是从下往上扫描。即数组中的第一行是图片中最后一行的数据。这样我们代码中选取的坐标系统与图片中常用的坐标是相符的

    • 全局变量的含义

      1
      2
      3
      4
      5
      6
      7
      const double pi=3.14; //定义常量pi
      int ph,pw, //原始图像的高度和宽度
      psize, //原始图像的字节数
      lsize, //原始图像每行的字节数
      cx,cy; //原始图像的中心点的坐标。
      BMPFILEHEADER thisBmpFileHeader; // 定义一个 BMP 文件头的结构体
      BMPINF thisBmpInfo; // 定义一个 BMP 文件信息结构体

      主函数中读入in.bmp,将文件头信息存储到 thisBmpFileHeaderthisBmpInfo 中。从文件头信息中读取 ph,pw 的值。

      根据公式 $\begin{cases}psize = pwph3\size=pw*3\cx=pw/2\cy=ph/2\end{cases}$ 计算出各个变量的值,作为全局变量供所有函数使用。

  • 函数的作用及参数表

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    //fp指向的文件的文件头读入到thisBmpFileHeader和thisBmpInfo中。
    void readFileHead(FILE *fp);
    //thisBmpFileHeader和thisBmpInfo中的信息写入到fp指向的文件中。
    void writeFileHead(FILE *fp);
    //进行平移操作,dx,dy为坐标移动的增量。flag=0时,画布增大,flag=1时,画布不增大。
    void Translation(BYTE *p,int dx,int dy,int flag);
    //对图像逆时针旋转theta角度(双线性插值法)
    void Rotation(BYTE *p,double theta);
    //对图像逆时针旋转theta角度(先旋转,后补全空缺的像素点)
    void Rotation2(BYTE *p,double theta);
    //对图像投影theta角度,flag=0 -> X轴方向投影,flag=0 -> y轴方向投影。
    void Shear(BYTE *p,double theta,int flag);
    //对图像放缩变换,a,b分别表示横纵坐标方向的比例。
    void Scale(BYTE *p,float a,float b);
    //对图像进行对称变换,flag=0时x轴,flag=1时y轴
    void Mirror(BYTE *p,int flag);
    //获取原图中(x,y)像素点绕图片中心逆时针旋转theta后的坐标。
    double GetRolX(int x,int y,double theta);
    double GetRolY(int x,int y,double theta);
    //获取原图经旋转后得到的新的宽度和高度,分别存储在x,y指针中
    int GetRolHW(int *x,int *y,double theta);
    //(x,y)在原图中双线性插值后的结果。 _ 变量表示RGB三个分量之一
    int Interpolation(BYTE *p,double x,double y,int _);
    //输出图像,图片名称为 name
    void OutPutImg(BYTE *pOUT,int nw,int nh,char *name);
  • 文件读入输出。

    1
    2
    3
    void readFileHead(FILE *fp);
    void writeFileHead(FILE *fp);
    void OutPutImg(BYTE *pOUT,int nw,int nh,char *name);

    这三个函数的功能与之前实验中功能相同并且实现起来很简单,不做赘述。

    • 双线性插值算法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      int Interpolation(BYTE *p,double x,double y,int _)
      {
      int x1,x2,y1,y2;
      x1=floor(x);y1=floor(y);
      x2=x1+1;y2=y1+1;
      if(x1>=0&&x1<pw-1&&y1>=0&&y1<ph-1)
      {
      float radio11=x-x1,radio12=x2-x,
      radio21=y-y1,radio22=y2-y;
      int tmp1,tmp2,tmp3,tmp4;
      tmp1=y1*lsize+x1*3;
      tmp2=y1*lsize+x2*3;
      tmp3=y2*lsize+x1*3;
      tmp4=y2*lsize+x2*3;
      int X1,X2;
      X1=p[tmp1+_]*radio11+p[tmp2+_]*radio12;
      X2=p[tmp3+_]*radio11+p[tmp4+_]*radio12;
      return X1*radio21+X2*radio22;
      }
      else
      return 0;
      }

      利用公式 $$C’(x’,y’)=radio_{21}*(radio_{11}*C(x_1,y_1)+radio_{12}C(x_1,y_2))\\qquad\qquad+ radio_{22}(radio_{11}*C(x_2,y_1)+radio_{12}*C(x_2,y_2))$$
      进行模拟即可

    • 平移 Translation

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      Translation(BYTE *p,int dx,int dy,int flag)
      if(dx%4) dx=(dx/4+1)*4;//保证x轴方向的偏移量为4的倍数
      int i,j,tmp,n;
      if(flag==1)
      for each pixel (i,j) in new img
      x:=j-dx
      y:=i-dy
      if(x>=pw) x:=x-pw;
      if(x<0) x:=x+pw;
      if(y>=ph) y:=y-ph;
      if(y<0) y:=y+ph;
      pOUT(i,j) := p(y,x)
      end
      end
      else
      Get the width nw and height nh of new img.
      Get the coordidates of the pixels which are border.
      Assume (x0,y0) is the right-top border
      for each pixel (i,j) in new img
      x:=j-x0-dx
      y:=i-y0-dy
      if (x,y)in the old img
      pOUT(i,j) := p(y,x)
      end
      end

      flag的值是1时,我们做的平移操作不改变图像的尺寸。按照公式$$ \left{ \begin{aligned} x = x’-dx\y = y’-dy \end{aligned} \right. $$ 计算得到 $(x,y)$ 的值,如果 $(x,y)$ 超出了图片的范围,我们把它移动到图片另一侧。

      flag的值是0时,我们做的平移操作会改变图像的尺寸。首先获取新的图像的尺寸。其次,根据不同的平移情况,获取有图案的部分位于画布的位置,也就是在画布上的四个边界顶点的坐标。设左上点的坐标是$ (x_0,y_0)$ 。则坐标公式为$$ \left{ \begin{aligned} x = j-x_0-dx\y = i-y_0-dy \end{aligned} \right. $$ 。最后进行对应的赋值。

    • 旋转(插值法)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Rotation(BYTE *p,double theta)
    GetRolHW(&nw,&nh,theta);
    dX:=(nw-pw)/2,dY:=(nh-ph)/2;
    for each pixel (i,j) in new img
    a:=j-dX-cx+cx*cos(theta)-cy*sin(theta);
    b:=i-dY-cy+cy*cos(theta)+cx*sin(theta);
    x:=b*sin(theta)+a*cos(theta);
    y:=b*cos(theta)-a*sin(theta);
    pOUT=Interpolation(p,x,y,_);
    end
    end
      $\begin{cases}a=x'-dx-cx+cx*cos\theta-cy*sin\theta\\b=y'-dy-cy+cy*cos\theta+cx*sin\theta\end{cases}$   $$ \left\{ \begin{aligned}  x = b*sin\theta +a*cos\theta \\y = b*cos\theta-a*sin\theta \end{aligned} \right. $$ 
    

    首先利用函数 GetRolHW 获取新图像的宽度和高度。

    然后根据上面的公式计算得到每个像素点对应的初始坐标(x,y)。

    然后用函数 Interpolation 做双线性插值。

  • 旋转(补空法)

      
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    void Rotation2(BYTE *p,double theta)//对图像旋转theta角度
    GetRolHW(&nw,&nh,theta);
    dX=(nw-pw)/2,dY=(nh-ph)/2
    Get the value of fMinX、fMaxX、fMinY、fMaxY //图像的边界
    for each pixel (i,j) in origin img
    get (x,y) rotate (i,j)
    Mark (i,j)
    end
    for each pixel (i,j) in new img
    if (i,j) is not marked
    if (i,j) is the last marked pixel in the row
    (i,j) = the pixel before it which is marked
    if (i,j) is the first marked pixel in the row
    (i,j) = the pixel after it which is marked
    if (i,j) is in the middle
    (i,j) = radio1*pafter + radio2*pbefore
    end
    end
    end
    首先利用函数 ***GetRolHW*** 获取新图像的宽度和高度。 遍历原始图像中的各个像素点,然后进行旋转操作得到其对应的新图像的坐标,赋值后标记这些点。 遍历新图像中的各个像素点,如果其在图像边界之内,并且没有被标记,则用新图像中其周围的像素点的信息来计算像素点的值。 如果这个像素点之前的所有点均未被标记,则这个像素点的值为其后第一个被标记的点的像素点。 如果这个像素点之后的所有点均未被标记,则这个像素点的值为其前第一个被标记的点的像素点。 如果这个像素点前后均有点被标记,则这个像素点的值为其前第一个被标记的点的像素点和其后第一个被标记的点的像素点的加权平均。

    ```


  • 放缩、对称和投影

    这三个函数的操作非常简单,利用原理部分的公式,计算得到(x,y)后,进行插值算法。

    其代码和旋转操作代码十分相似,只有坐标计算公式不同。

  • 结果展示

    平移

    Translation0

    平移

    Translation1

    X轴方向对称

    MirrorX

    Y轴方向对称

    MirrorY

    逆时针旋转60度

    Rotation_60_degree

    放缩

    Scale

    水平投影30度

    Shear_X_30_degree

    垂直投影30度

    Shear_Y_30_degree

  • 两种不同算法的旋转操作的结果比较

    对原图和两种算法得到的旋转图像分别作出直方图如下

    image-20191117141147551

    image-20191117141200983

    image-20191117141211497

    根据直方图我们可以得出结论两种算法得到的旋转图像均保留了图像的样貌。

四、心得体会

  • 对图像变换的理解

    通过这次实验,我明白了图像的几何变换就是对像素点的坐标进行几何变换,可以借助矩阵运算来更加方便的计算坐标。

  • 对插值法的理解
    我们插值的目的在于,通过几何变换计算坐标时,并不总是能够得到整数的值。当我们计算得到的值是小数的时候,就需要利用这个点周围的像素点RGB值来求其对应的值。很明显距离越远的的点对它造成的影响应当越少。自然而然可以想到按照距离进行加权平均,这也正是双线性插值的公式

  • 写代码过程中的问题

    • 坐标到数组下标的转化

      在根据坐标计算其在数组中对应的下标时,由于新老图片尺寸等问题,在计算时犯了很多低级错误,导致出现奇怪的bug。思考之后有两个改进方案。其一是改进存储方法,不使用一个数组来存储整张图片信息,而是用一个结构体数组来存储,每个结构体含有R,G,B三个成员。这样在计算坐标的时候,就不会出现问题。第一个方法则是把这个计算关系写成一个函数。

    • 最开始写的时候没有对程序有好的架构。导致不同的几何变换中出现了大量重复的操作,代码显得十分冗长。后来进行了一次重构,将重复的操作提炼成函数,这样使代码更加简洁明了。