# 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图像为例

### 4.1 文件头验证
用**UltraEdit**打开lena图像后,可以看到图像的具体数据。下图中依次框出了文件头中的文件类型、位图大小、保留值、偏移量。

根据每个名称的字节数,依次读取数据,并转换为10进制,算出实际值,如下表所示:
表3 lena图像文件头数据
| 名称 | 占用空间 | 说明 | 实际数据 |
| ------------- | :------: | :--------------------------------------------: | ---------------- |
| bfType | 2字节 | 说明文件类型,该值必须为0x4D42,也就是字符“BM” | 0x4D42 (BM) |
| bfSize | 4字节 | 说明位图文件大小,以字节为单位 | 0x41EE6 (270054) |
| bfReserved1/2 | 4字节 | 保留字,必须设置为0 | 0 |
| bfOffBits | 4字节 | 说明从文件头开始到实际的图像数据之间的偏移量 | 0x36 (54) |
运行代码后,文件头输出信息如下:

经过对比,实际读入的文件头信息与表3信息一致。
### 4.2 信息头验证
下图中依次框出了信息头中每个成员的具体数值。

根据每个名称的字节数,依次读取数据,并转换为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 |
运行代码后,文件头输出信息如下:

经过对比,实际读入的信息头信息与表4信息一致。
### 4.3 图像读取效果
下面的代码调用了自己写的函数**imreadBMP**,将读取的图像数据保存在mybmp变量中,然后调用opencv库函数imshow显示图像。
```c++
Mat mybmp = cv2.imreadBMP("lena.bmp");
imshow("mybmp", mybmp);
```

可以看到,读取的图像和原始图像一致。
### 4.4 图像写入效果
下面的代码调用了自己写的函数imwriteBMP,将上面读取的图像数据写入了myfun.bmp文件中。
```c++
cv2.imwriteBMP("myfun.bmp", mybmp);
```

可以看到,写入的图像和原图像一致。