Github个人博客:https://joeyos.github.io
实现方案由三部分组成:主函数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