实现方案由三部分组成:主函数main.cpp、函数实现zq_CV.cpp、程序头文件zq_CV.h。其中,主函数main.cpp主要实现图像读写、图像旋转、图像缩放等函数的调用。zq_CV.cpp主要实现图像读写、图像旋转、图像缩放等函数的功能实现。头文件zq_CV.h主要对BMP图像的格式以及图像读写、旋转、缩放函数的定义。
这里主要介绍图像读写、旋转、缩放的函数实现部分,对BMP图像的格式不做详细介绍。其中,图像的结构体定义为:
typedef struct
{
int width;//图像的宽度
int height;//图像的高度
int channels;//图像通道数
unsigned char* imageData;//像素信息
}ZqImage;
图像读写
图像的读取函数定义为:ZqImage* imread(char* path),其输入为图像的路径,输出为一个图像结构体指针。
首先打开文件,读取BMP文件头结构体、信息头结构体,然后判断图像信息头结构体里的biBitCount(像素位数),若biBitCount为8表示此图像为灰度图,若为24则为RGB彩色图。
然后分配图像空间,读取图像颜色表,依次读取图像数据。由于windows规定每一个扫描行必须为4的倍数,不足补0,故设置offset判断图像每行是否为4的倍数,否则继续读取0。
最后,读取完全部数据,关闭文件,返回图像结构体指针bmpImg。
图像的写入函数定义为:bool imwrite(char* path, ZqImage* bmpImg),其输入为图像的结构体指针,输出为图像保存路径。
首先打开文件,写入BMP文件头标识符,然后判断图像结构体指针里的图像通道数。若通道数为1则为灰度图,依次写入灰度图的文件头、信息头、颜色表,然后写入图像像素信息,扫描行为4的倍数,不足补0。若通道数为3则为彩色图,依次写入彩色图的文件头、信息头,然后写入图像3个通道的像素信息,扫描行为4的倍数,不足补0。
最后,写完全部图像数据,关闭文件,返回bool型变量true。用于判断写入成功与否。
图像旋转
图像的旋转函数定义为:ZqImage* imrotate(ZqImage* bmpImg,int Angle),其中输入为图像结构体指针及旋转角度,输出为旋转后图像的结构体指针。
首先新建旋转后图像的结构体指针,图像长宽、通道数与原始图像一致。
然后,定义旋转前后的图像中心的坐标:
int midX_pre,midY_pre,midX_aft,midY_aft;
中心的坐标为各图像长宽的一半。然后,定义旋转前对于的像素点坐标:
int pre_i,pre_j;
然后将旋转角度转换为弧度,为旋转图像分配内存,初始化旋转图片的结构体信息。
判断图像的通道数,若为1,则为灰度图,然后对旋转后的坐标进行坐标反变换:
after_i = i - midX_aft;
after_j = j - midY_aft;
pre_i = (int)(cos((double)angle) * after_i - sin((double)angle) * after_j) + midX_pre;
pre_j = (int)(sin((double)angle) * after_i + cos((double)angle) * after_j) + midY_pre;
其中after_i、after_j为中心点在原点(0,0)对应旋转图像的坐标。pre_i、pre_j为旋转图像旋转逆变换对应的原图像坐标。然后判断pre_i、pre_j是否落在原图像内,若在原图像内则把原图像对应的对应像素信息赋给旋转图像的对应坐标。逆变换相当于进行了最近邻插值,目的是防止直接进行旋转变换,出现小数坐标,最后旋转图像部分像素丢失的情况。
若图像的通道数为3,则判断逆变换后的坐标满足落在原图像区域内后,需要分3次依次把原图像RGB像素信息赋给旋转图像的对应坐标。
最后,返回旋转图像的结构体指针。
图像缩放
图像的缩放可以类比图像的旋转,都是坐标的变换,然后把原图像坐标的像素值赋给变换图像对应的坐标。相比图像旋转,图像的缩放可以不必进行图像中心点的搬移,具体过程参考图像旋转。
源代码
main.cpp
//#include "stdafx.h"
#include<stdio.h>
#include "ZQ_CV.h"
int main(int argc, char* argv[])
{
ZqImage* img = imread("lena.bmp");//读取原始图像(灰度或彩色)
ZqImage* imgRot = imrotate(img,73);//旋转原始图像73°
ZqImage* imgSca = imscale(img,0.5,1.3);//缩放图像宽0.5倍,高1.3倍
//输出图像
imwrite("Result.bmp", img);
imwrite("RotResult.bmp", imgRot);
imwrite("ScaResult.bmp", imgSca);
getchar();
return 0;
}
zq_CV.h
#ifndef ZQ_CV_H
#define ZQ_CV_H
#define PI 3.1415926
/*按字节对齐Start
如果不按字节对齐,
第一个struct定义为16bit,
而BMP文件中实际为14bit,
故读取会错位。
--zhangquan
*/
//位图文件头bf
#pragma pack (push ,1)
typedef struct
{
unsigned short bfType; //文件标识符BM,0x424D 2bit
unsigned long bfSize;//文件的大小 4bit
unsigned short bfReserved1; //保留值,必须设置为0 2bit
unsigned short bfReserved2; //保留值,必须设置为0 2bit
unsigned long bfOffBits;//文件头的最后到图像数据位开始的偏移量 4bit
} BMPFileHeader;
//位图信息头bi
typedef struct
{
unsigned long biSize;//信息头的大小
long biWidth; //图像宽度
long biHeight; //图像高度
unsigned short biPlanes; //图像的位面数
unsigned short biBitCount;//每个像素的位数
unsigned long biCompression;//压缩类型
unsigned long biSizeImage;//图像大小,字节
long biXPelsPerMeter; //水平分辨率
long biYPelsPerMeter; //垂直分辨率
unsigned long biClrUsed; //使用的色彩数
unsigned long biClrImportant;//重要的颜色数
} BMPInfoHeader;
//颜色表
typedef struct
{
unsigned char rgbBlue; //蓝色分量
unsigned char rgbGreen; //绿色分量
unsigned char rgbRed; //红色分量
unsigned char rgbReserved; //保留值
} RgbQuad;
typedef struct
{
int width;
int height;
int channels;
unsigned char* imageData;
}ZqImage;
#pragma pack (pop)
/*按字节对齐End*/
ZqImage* imread(char* path);
bool imwrite(char* path, ZqImage* bmpImg);
ZqImage* imrotate(ZqImage* bmpImg,int Angle);
ZqImage* imscale(ZqImage* bmpImg,double dy,double dx);
#endif
zq_CV.cpp
#include "zq_CV.h"
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
ZqImage* imread(char* path)
{
ZqImage* bmpImg;
FILE* pFile;
BMPFileHeader bmpFileHeader;
BMPInfoHeader bmpInfoHeader;
int channels = 1;
int width = 0;
int height = 0;
int step = 0;
int offset = 0;
unsigned char pixVal;//像素指针
RgbQuad* quad;//BMP图像颜色表
int i, j, k;
bmpImg = (ZqImage*)malloc(sizeof(ZqImage));
if ((pFile = fopen(path, "rb")) == NULL)
{
printf("Cann't open the file!\n");
free(bmpImg);
return NULL;
}
//读取文件头
fread(&bmpFileHeader, sizeof(BMPFileHeader), 1, pFile);
printf("=========================================== \n");
printf("BMP文件头信息:\n");
printf("文件标识符 :0X%X \n", bmpFileHeader.bfType);
printf("文件大小:%d \n", bmpFileHeader.bfSize);
printf("保留字:%d \n", bmpFileHeader.bfReserved1);
printf("保留字:%d \n", bmpFileHeader.bfReserved2);
printf("位图数据偏移字节数:%d \n", bmpFileHeader.bfOffBits);
//读取信息头
fread(&bmpInfoHeader, sizeof(BMPInfoHeader), 1, pFile);
printf("=========================================== \n");
printf("BMP文件信息头\n");
printf("结构体长度:%d \n", bmpInfoHeader.biSize);
printf("位图宽度:%d \n", bmpInfoHeader.biWidth);
printf("位图高度:%d \n", bmpInfoHeader.biHeight);
printf("位图平面数:%d \n", bmpInfoHeader.biPlanes);
printf("颜色位数:%d \n", bmpInfoHeader.biBitCount);
printf("压缩方式:%d \n", bmpInfoHeader.biCompression);
printf("实际位图数据占用的字节数:%d \n", bmpInfoHeader.biSizeImage);
printf("X方向分辨率:%d \n", bmpInfoHeader.biXPelsPerMeter);
printf("Y方向分辨率:%d \n", bmpInfoHeader.biYPelsPerMeter);
printf("使用的颜色数:%d \n", bmpInfoHeader.biClrUsed);
printf("重要颜色数:%d \n", bmpInfoHeader.biClrImportant);
printf("=========================================== \n");
if (bmpInfoHeader.biBitCount == 8)
{
printf("\n该图像为灰度图! \n");
channels = 1;
width = bmpInfoHeader.biWidth;
height = bmpInfoHeader.biHeight;
//windows规定每一个扫描行为4的倍数,不足补0
offset = (channels*width)%4;
if (offset != 0)
{
offset = 4 - offset;
}
bmpImg->width = width;
bmpImg->height = height;
bmpImg->channels = 1;
//分配图像空间
bmpImg->imageData = (unsigned char*)malloc(sizeof(unsigned char)*width*height);
//迭代步长
step = channels*width;
//读取图像颜色表
quad = (RgbQuad*)malloc(sizeof(RgbQuad)*256);
fread(quad, sizeof(RgbQuad), 256, pFile);
free(quad);
//读取灰度图像数据
for (i=0; i<height; i++)
{
for (j=0; j<width; j++)
{
fread(&pixVal, sizeof(unsigned char), 1, pFile);
bmpImg->imageData[(height-1-i)*step+j] = pixVal;
}
if (offset != 0)
{
for (j=0; j<offset; j++)
{
fread(&pixVal, sizeof(unsigned char), 1, pFile);
}
}
}
}
else if (bmpInfoHeader.biBitCount == 24)
{
printf("\n该图像为彩色图! \n");
channels = 3;
width = bmpInfoHeader.biWidth;
height = bmpInfoHeader.biHeight;
bmpImg->width = width;
bmpImg->height = height;
bmpImg->channels = 3;
bmpImg->imageData = (unsigned char*)malloc(sizeof(unsigned char)*width*3*height);
step = channels*width;
//windows规定每一个扫描行为4的倍数,不足补0
offset = (channels*width)%4;
if (offset != 0)
{
offset = 4 - offset;
}
//读取彩色图像数据
for (i=0; i<height; i++)
{
for (j=0; j<width; j++)
{
for (k=0; k<3; k++)
{
fread(&pixVal, sizeof(unsigned char), 1, pFile);
bmpImg->imageData[(height-1-i)*step+j*3+k] = pixVal;
}
}
if (offset != 0)
{
for (j=0; j<offset; j++)
{
fread(&pixVal, sizeof(unsigned char), 1, pFile);
}
}
}
}
return bmpImg;
}
bool imwrite(char* path, ZqImage* bmpImg)
{
FILE *pFile;
BMPFileHeader bmpFileHeader;
BMPInfoHeader bmpInfoHeader;
int width = 0;
int height = 0;
int step = 0;
int channels = 1;
int i, j;
int offset;
unsigned char pixVal = '\0';
RgbQuad* quad;
width = bmpImg->width;
height = bmpImg->height;
channels = bmpImg->channels;
step = width * channels;
pFile = fopen(path, "wb");
if (!pFile)
{
return false;
}
//写入文件头标识符
bmpFileHeader.bfType = 0x4D42;
if (channels == 1)//8位,灰度图
{
//windows规定每一个扫描行为4的倍数,不足补0
offset = step%4;
if (offset != 0)
{
offset=4-offset;
step += offset;
}
//写入文件头
bmpFileHeader.bfSize = 54 + 256*4 + width;
bmpFileHeader.bfReserved1 = 0;
bmpFileHeader.bfReserved2 = 0;
bmpFileHeader.bfOffBits = 54 + 256*4;
fwrite(&bmpFileHeader, sizeof(BMPFileHeader), 1, pFile);
//写入信息头
bmpInfoHeader.biSize = 40;
bmpInfoHeader.biWidth = width;
bmpInfoHeader.biHeight = height;
bmpInfoHeader.biPlanes = 1;
bmpInfoHeader.biBitCount = 8;
bmpInfoHeader.biCompression = 0;
bmpInfoHeader.biSizeImage = height*step;
bmpInfoHeader.biXPelsPerMeter = 0;
bmpInfoHeader.biYPelsPerMeter = 0;
bmpInfoHeader.biClrUsed = 256;
bmpInfoHeader.biClrImportant = 256;
fwrite(&bmpInfoHeader, sizeof(BMPInfoHeader), 1, pFile);
quad = (RgbQuad*)malloc(sizeof(RgbQuad)*256);
for (i=0; i<256; i++)
{
quad[i].rgbBlue = i;
quad[i].rgbGreen = i;
quad[i].rgbRed = i;
quad[i].rgbReserved = 0;
}
fwrite(quad, sizeof(RgbQuad), 256, pFile);
free(quad);
for (i=height-1; i>-1; i--)
{
for (j=0; j<width; j++)
{
pixVal = bmpImg->imageData[i*width+j];
fwrite(&pixVal, sizeof(unsigned char), 1, pFile);
}
if (offset!=0)
{
for (j=0; j<offset; j++)
{
pixVal = 0;
fwrite(&pixVal, sizeof(unsigned char), 1, pFile);
}
}
}
}
else if (channels == 3)//24位,通道,彩图
{
//windows规定每一个扫描行为4的倍数,不足补0
offset = step%4;
if (offset != 0)
{
offset=4-offset;
step += 4-offset;
}
//写入文件头
bmpFileHeader.bfSize = height*step + 54;
bmpFileHeader.bfReserved1 = 0;
bmpFileHeader.bfReserved2 = 0;
bmpFileHeader.bfOffBits = 54;
fwrite(&bmpFileHeader, sizeof(BMPFileHeader), 1, pFile);
//写入信息头
bmpInfoHeader.biSize = 40;
bmpInfoHeader.biWidth = width;
bmpInfoHeader.biHeight = height;
bmpInfoHeader.biPlanes = 1;
bmpInfoHeader.biBitCount = 24;
bmpInfoHeader.biCompression = 0;
bmpInfoHeader.biSizeImage = height*step;
bmpInfoHeader.biXPelsPerMeter = 0;
bmpInfoHeader.biYPelsPerMeter = 0;
bmpInfoHeader.biClrUsed = 0;
bmpInfoHeader.biClrImportant = 0;
fwrite(&bmpInfoHeader, sizeof(BMPInfoHeader), 1, pFile);
for (i=bmpImg->height-1; i>-1; i--)
{
for (j=0; j<bmpImg->width; j++)
{
pixVal = bmpImg->imageData[i*width*3+j*3+0];
fwrite(&pixVal, sizeof(unsigned char), 1, pFile);
pixVal = bmpImg->imageData[i*width*3+j*3+1];
fwrite(&pixVal, sizeof(unsigned char), 1, pFile);
pixVal = bmpImg->imageData[i*width*3+j*3+2];
fwrite(&pixVal, sizeof(unsigned char), 1, pFile);
}
if (offset!=0)
{
for (j=0; j<offset; j++)
{
pixVal = 0;
fwrite(&pixVal, sizeof(unsigned char), 1, pFile);
}
}
}
}
fclose(pFile);
return true;
}
ZqImage* imrotate(ZqImage* bmpImg,int Angle)
{
//图片旋转处理
ZqImage* bmpImgRot;
double angle;//要旋转的弧度数
int width = 0;
int height = 0;
int step = 0;
int Rot_step = 0;
int channels = 1;
int i, j, k;
width = bmpImg->width;
height = bmpImg->height;
channels = bmpImg->channels;
int midX_pre,midY_pre,midX_aft,midY_aft;//旋转前后的中心点的坐标
midX_pre = width / 2;
midY_pre = height / 2;
int pre_i,pre_j,after_i,after_j;//旋转前后对应的像素点坐标
angle = 1.0 * Angle * PI / 180;
//初始化旋转后图片的信息
bmpImgRot = (ZqImage*)malloc(sizeof(ZqImage));
bmpImgRot->channels = channels;
bmpImgRot->width = bmpImg->width;
bmpImgRot->height = bmpImg->height;
midX_aft =bmpImgRot->width / 2;
midY_aft = bmpImgRot->height / 2;
step = channels * width;
Rot_step = channels * bmpImgRot->width;
bmpImgRot->imageData = (unsigned char*)malloc(sizeof(unsigned char)*bmpImgRot->width*bmpImgRot->height*channels);
if (channels == 1)
{
//初始化旋转图像
for (i=0; i<bmpImgRot->height; i++)
{
for (j=0; j<bmpImgRot->width; j++)
{
bmpImgRot->imageData[(bmpImgRot->height-1-i)*Rot_step+j] = 0;
}
}
//坐标变换
for(i = 0;i < bmpImgRot->height;i++)
{
for(j = 0;j < bmpImgRot->width;j++)
{
after_i = i - midX_aft;
after_j = j - midY_aft;
pre_i = (int)(cos((double)angle) * after_i - sin((double)angle) * after_j) + midX_pre;
pre_j = (int)(sin((double)angle) * after_i + cos((double)angle) * after_j) + midY_pre;
if(pre_i >= 0 && pre_i < height && pre_j >= 0 && pre_j < width)//在原图范围内
bmpImgRot->imageData[i * Rot_step + j] = bmpImg->imageData[pre_i * step + pre_j];
}
}
}
else if (channels == 3)
{
//初始化旋转图像
for(i=0; i<bmpImgRot->height; i++)
{
for(j=0; j<bmpImgRot->width; j++)
{
for(k=0; k<3; k++)
{
bmpImgRot->imageData[(bmpImgRot->height-1-i)*Rot_step+j*3+k] = 0;
}
}
}
//坐标变换
for(i = 0;i < bmpImgRot->height;i++)
{
for(j = 0;j < bmpImgRot->width;j++)
{
after_i = i - midX_aft;
after_j = j - midY_aft;
pre_i = (int)(cos((double)angle) * after_i - sin((double)angle) * after_j) + midX_pre;
pre_j = (int)(sin((double)angle) * after_i + cos((double)angle) * after_j) + midY_pre;
if(pre_i >= 0 && pre_i < height && pre_j >= 0 && pre_j < width)//在原图范围内
for(k=0; k<3; k++)
{
bmpImgRot->imageData[i * Rot_step + j*3 +k] = bmpImg->imageData[pre_i * step + pre_j*3 + k];
}
}
}
}
return bmpImgRot;
}
ZqImage* imscale(ZqImage* bmpImg,double dy,double dx)
{
//图片缩放处理
ZqImage* bmpImgSca;
int width = 0;
int height = 0;
int channels = 1;
int step = 0;
int Sca_step = 0;
int i, j, k;
width = bmpImg->width;
height = bmpImg->height;
channels = bmpImg->channels;
int pre_i,pre_j,after_i,after_j;//缩放前对应的像素点坐标
//初始化缩放后图片的信息
bmpImgSca = (ZqImage*)malloc(sizeof(ZqImage));
bmpImgSca->channels = channels;
bmpImgSca->width = (int)(bmpImg->width*dy + 0.5);
bmpImgSca->height = (int)(bmpImg->height*dx + 0.5);
step = channels * width;
Sca_step = channels * bmpImgSca->width;
bmpImgSca->imageData = (unsigned char*)malloc(sizeof(unsigned char)*bmpImgSca->width*bmpImgSca->height*channels);
if (channels == 1)
{
//初始化缩放图像
for (i=0; i<bmpImgSca->height; i++)
{
for (j=0; j<bmpImgSca->width; j++)
{
bmpImgSca->imageData[(bmpImgSca->height-1-i)*Sca_step+j] = 0;
}
}
//坐标变换
for(i = 0;i < bmpImgSca->height;i++)
{
for(j = 0;j < bmpImgSca->width;j++)
{
after_i = i;
after_j = j;
pre_i = (int)(after_i / dx + 0);
pre_j = (int)(after_j / dy + 0);
if(pre_i >= 0 && pre_i < height && pre_j >= 0 && pre_j < width)//在原图范围内
{
bmpImgSca->imageData[i * Sca_step + j] = bmpImg->imageData[pre_i * step + pre_j];
}
}
}
}
else if (channels == 3)
{
//初始化缩放图像
for(i=0; i<bmpImgSca->height; i++)
{
for(j=0; j<bmpImgSca->width; j++)
{
for(k=0; k<3; k++)
{
bmpImgSca->imageData[(bmpImgSca->height-1-i)*Sca_step+j*3+k] = 0;
}
}
}
//坐标变换
for(i = 0;i < bmpImgSca->height;i++)
{
for(j = 0;j < bmpImgSca->width;j++)
{
after_i = i;
after_j = j;
pre_i = (int)(after_i / dx + 0.5);
pre_j = (int)(after_j / dy + 0.5);
if(pre_i >= 0 && pre_i < height && pre_j >= 0 && pre_j < width)//在原图范围内
for(k=0; k<3; k++)
{
bmpImgSca->imageData[i * Sca_step + j*3 +k] = bmpImg->imageData[pre_i * step + pre_j*3 + k];
}
}
}
}
return bmpImgSca;
}