# opencv-cpp **Repository Path**: martin64/opencv-cpp ## Basic Information - **Project Name**: opencv-cpp - **Description**: 用C++实现一些常用的opencv函数 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-06-28 - **Last Updated**: 2021-08-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## 1. 问题描述 - 分析图像格式BMP –可借助Matlab体会图像的读写和显示。 - 利用C语言编写程序,实现图像的输入和输出和显示。 –自行编写BMP文件的读写。 –调用开源库实现其他若干常见图像和视频文件格式的输入和输出。 –设计功能较完整的界面。 ## 2. 技术背景 - opencv - Visual Studio 2019 ## 3. 解决方案 ### 3.1 BMP文件结构 BMP文件由4部分组成: 1. 位图文件头(bitmap-file header) 2. 位图信息头(bitmap-information header) 3. 调色板 4. 位图数据 BMP文件整体结构如下图所示: #### 3.1.1 位图文件头 位图文件头的数据结构如下: ```c++ typedef struct tagBITMAPFILEHEADER { //WORD bfType; //文件类型 DWORD bfSize; //文件大小 WORD bfReserved1; //保留字 WORD bfReserved2; //保留字 DWORD bfOffBits; //文件头到实际图像数据之间的偏移量 }BITMAPFILEHEADER; ``` 结构体中的成员说明如下:
表1 BMP文件头信息说明
| 名称 | 占用空间 | 说明 | | ------------- | :------: | :--------------------------------------------: | | bfType | 2字节 | 说明文件类型,该值必须为0x4D42,也就是字符“BM” | | bfSize | 4字节 | 说明位图文件大小,以字节为单位 | | bfReserved1/2 | 4字节 | 保留字,必须设置为0 | | bfOffBits | 4字节 | 说明从文件头开始到实际的图像数据之间的偏移量 | #### 3.1.2 位图信息头 位图信息头的数据结构如下: ```c++ typedef struct tagBITMAPINFOHEADER { DWORD biSize; //位图信息头大小 LONG biWidth; //位图宽度 LONG biHeight; //位图高度 WORD biPlanes; //颜色平面数 WORD biBitCount; //每个像素的位数 DWORD biCompression; //压缩方式 DWORD biSizeImage; //位图全部像素占用的字节数 LONG biXPelsPerMeter; //水平方向分辨率 LONG biYPelsPerMeter; //垂直方向分辨率 DWORD biClrUsed; //使用的颜色数 DWORD biClrImportant; //重要颜色数 }BITMAPINFOHEADER; ``` 结构体中的成员说明如下:
表2 BMP文件头信息说明
| 名称 | 占用空间 | 说明 | | --------------- | -------- | ------------------------- | | biSize | 4字节 | 位图信息头的大小 | | biWidth | 4字节 | 位图的宽度,单位是像素 | | biHeight | 4字节 | 位图的高度,单位是像素 | | biPlanes | 2字节 | 颜色平面数,固定值1 | | biBitCount | 2字节 | 每个像素的位数 | | biCompression | 4字节 | 压缩方式,0为不压缩 | | biSizeImage | 4字节 | 位图全部像素占用的字节数 | | biXPelsPerMeter | 4字节 | 水平方向分辨率(像素/米) | | biYPelsPerMeter | 4字节 | 垂直方向分辨率(像素/米) | | biClrUsed | 4字节 | 使用的颜色数 | | biClrImportant | 4字节 | 重要颜色数 | #### 3.1.3 调色板 调色板是一张映射表,标识颜色索引号与其代表的颜色的对应关系。 ```c++ typedef struct tagRGBQUAD { BYTE rgbBlue; BYTE rgbGreen; BYTE rgbRed; BYTE rgbReserved; }RGBQUAD; ``` #### 3.1.4 位图数据 **windows**在早期开发BMP文件系统时,依照笛卡尔坐标系设计了BMP位图的数据存储结构,即图像的原点是上左下角。但是现在的图像基本上是以图像左上角为原点存储图像数据,这就导致如果按照现在的方式读取BMP位图显示的图像在垂直方向上和原图是相反的。所以在读取BMP位图数据时,应该把第一行位图数据放在存储矩阵的最后一行。 ```c++ typedef struct tagIMAGEDATA { BYTE red; BYTE green; BYTE blue; }IMAGEDATA; ``` ### 3.2 BMP文件读写 在C++中,读写文件需要用文件流对象(fstream),文件流对象有两个子类,分别是文件输入流对象(ifstream)和文件输出流对象(ofstream)。 ```c++ ifstream in(path,ios::binary); ofstream out(path, ios::binary); ``` 上面的代码分别定义了一个文件输入流对象和文件输出流对象,而且都是以二进制方式读写。 #### 3.2.1 读BMP文件 C++中,读取文件数据可以用文件输入流ifstrem类中的read函数。 函数原型**ifstream& read(char* s, streamsize n)** 其中,**s**为一个**char**型数组,用来保存读取得到的数据值,n为读取的数据长度。 下面的代码定义了一个文件输入流对象,连接到path所指的文件。通过**read**函数把**path**所指的文件的前**sizeof(bfType)**个字节的数据保存到**bfType**变量中,然后文件指针向后移动**sizeof(bfType)**个字节。 ```c++ ifstream in(path,ios::binary); WORD bfType; in.read((char*)(&bfType), sizeof(bfType)); ``` 上面有提到,**windows**在早期开发BMP文件系统时,依照笛卡尔坐标系设计了BMP位图的数据存储结构,即图像的原点是再左下角。但是**opencv**中显示图像时,原点时图像的左上角,所以两种存储格式在行数上是相反的,在列数上是相同的。下面的代码就是把BMP位图中数据依照**opencv**图像存储格式读了出来,使得**opencv**显示的图像和原图一致。 ```c++ if (channels == 1) { Mat dst = Mat(rows, cols, CV_8UC1); for (int i = rows - 1; i >= 0; i--) { for (int j = 0; j < cols; j++) { in.read((char*)(&dst.at(i, j)), sizeof(uchar)); } } in.close(); return dst; } ``` #### 3.2.1 写BMP文件 写BMP文件与读BMP文件类似,下面的代码定义了一个输出流对象**out**,然后在头文件中的**bfType**中写入了0x4d42。 ```c++ ofstream out(path, ios::binary); WORD bfType = 0x4d42; out.write((char*)(&bfType), sizeof(bfType)); ``` 在写文件时,一些参数是根据经验值预设的,**opencv**中存储像素的数据结构是MAT,下面的代码根据输入参数image的属性值,初始化了BMP位图的文件头与信息头。 ```c++ //写入文件头 BITMAPFILEHEADER strHead; int rows = image.rows; int cols = image.cols; strHead.bfOffBits = 54; strHead.bfReserved1 = 0; strHead.bfReserved2 = 0; strHead.bfSize = image.channels() * rows * cols + strHead.bfOffBits; out.write((char*)(&strHead), sizeof(strHead)); //写入信息头 BITMAPINFOHEADER strInfo; strInfo.biSize = 40; strInfo.biWidth = cols; strInfo.biHeight = rows; strInfo.biPlanes = 1; strInfo.biBitCount = image.channels() * 8; strInfo.biCompression = 0; strInfo.biSizeImage = image.channels() * rows * cols; strInfo.biXPelsPerMeter = 2834; strInfo.biYPelsPerMeter = 2834; strInfo.biClrUsed = 0; strInfo.biClrImportant = 0; ``` ## 4.实施示例 以lena图像为例 ![lena.bmp](https://gitee.com/martin64/opencv-cpp/raw/master/images/lena.bmp) ### 4.1 文件头验证 用**UltraEdit**打开lena图像后,可以看到图像的具体数据。下图中依次框出了文件头中的文件类型、位图大小、保留值、偏移量。 ![lena.bmp](https://gitee.com/martin64/opencv-cpp/raw/master/images/BMP_header.png) 根据每个名称的字节数,依次读取数据,并转换为10进制,算出实际值,如下表所示:
表3 lena图像文件头数据
| 名称 | 占用空间 | 说明 | 实际数据 | | ------------- | :------: | :--------------------------------------------: | ---------------- | | bfType | 2字节 | 说明文件类型,该值必须为0x4D42,也就是字符“BM” | 0x4D42 (BM) | | bfSize | 4字节 | 说明位图文件大小,以字节为单位 | 0x41EE6 (270054) | | bfReserved1/2 | 4字节 | 保留字,必须设置为0 | 0 | | bfOffBits | 4字节 | 说明从文件头开始到实际的图像数据之间的偏移量 | 0x36 (54) | 运行代码后,文件头输出信息如下: ![](https://gitee.com/martin64/opencv-cpp/raw/master/images/BMP_header_output.png) 经过对比,实际读入的文件头信息与表3信息一致。 ### 4.2 信息头验证 下图中依次框出了信息头中每个成员的具体数值。 ![lena.bmp](https://gitee.com/martin64/opencv-cpp/raw/master/images/BMP_Info.png) 根据每个名称的字节数,依次读取数据,并转换为10进制,算出实际值,如下表所示:
表4 lena图像信息头数据
| 名称 | 占用空间 | 说明 | 实际数据 | | --------------- | -------- | ------------------------- | ---------------- | | biSize | 4字节 | 位图信息头的大小 | 0x28 (40) | | biWidth | 4字节 | 位图的宽度,单位是像素 | 0x190 (400) | | biHeight | 4字节 | 位图的高度,单位是像素 | 0xE1 (255) | | biPlanes | 2字节 | 固定值1 | 1 | | biBitCount | 2字节 | 每个像素的位数 | 0x18 (24) | | biCompression | 4字节 | 压缩方式,0为不压缩 | 0 | | biSizeImage | 4字节 | 位图全部像素占用的字节数 | 0x41EB0 (270000) | | biXPelsPerMeter | 4字节 | 水平方向分辨率(像素/米) | 0xB12 (2834) | | biYPelsPerMeter | 4字节 | 垂直方向分辨率(像素/米) | 0xB12 (2834) | | biClrUsed | 4字节 | 使用的颜色数 | 0 | | biClrImportant | 4字节 | 重要颜色数 | 0 | 运行代码后,文件头输出信息如下: ![](https://gitee.com/martin64/opencv-cpp/raw/master/images/BMP_info_output.png) 经过对比,实际读入的信息头信息与表4信息一致。 ### 4.3 图像读取效果 下面的代码调用了自己写的函数**imreadBMP**,将读取的图像数据保存在mybmp变量中,然后调用opencv库函数imshow显示图像。 ```c++ Mat mybmp = cv2.imreadBMP("lena.bmp"); imshow("mybmp", mybmp); ``` ![](https://gitee.com/martin64/opencv-cpp/raw/master/images/myfunctionImread.png) 可以看到,读取的图像和原始图像一致。 ### 4.4 图像写入效果 下面的代码调用了自己写的函数imwriteBMP,将上面读取的图像数据写入了myfun.bmp文件中。 ```c++ cv2.imwriteBMP("myfun.bmp", mybmp); ``` ![](https://gitee.com/martin64/opencv-cpp/raw/master/images/myfunctionImwrite.bmp) 可以看到,写入的图像和原图像一致。