bmp 图片格式读入 cv::Mat 的 C++ 实现

Ⅰ、常见图片格式概述

常见的图片格式:bmp,jpeg,png,gif,tiff

  • bmp(BitMap):可以压缩,但绝大多数情况下未经压缩存储原彩图片,是 windows 平台特有的图片格式
  • jpeg、png:压缩图片格式,其中 jpeg(Joint Photographic Experts Group) 采用有损压缩,将不易被人眼察觉的图像颜色删除,从而达到较大的压缩比(可达到 2:1 甚至 40:1),因为 jpeg 格式的文件尺寸较小,下载速度快,所以是互联网上最广泛使用的格式;png 无损压缩,适合矢量图和几何图片的存储,支持图像透明,可以利用 Alpha 通道调节图像的透明度。二者相比 jpeg 在图片压缩方面有巨大优势,但采用有损压缩,图片质量有损失。一般截屏用 png 格式不但比 jpeg 质量高且文件还更小;防锯齿方面 png 非常有优势。
  • gif(Graphics Interchange Format):不仅可以是一张静止的图片,也可以是动画,并且支持透明背景图像,适用于多种操作系统,“体型”很小,网上很多小动画都是 gif 格式,但是其色域不太广,只支持 256 种颜色
  • tiff(Tagged Image File Format):可以支持 RGB、CMYK 等多种色彩模式,adobeRGB、proPhoto RGB 等多种色彩空间,8位、16位、32位等各种色彩深度,甚至也能保留图层和 alpha 通道。更重要的是,大部分的第三方软件、打印机,都能支持TIFF格式,适合专业领域图片存储。

Ⅱ、bmp 格式介绍

Ⅲ、bmp 图片读入 Mat 并显示

3-1. 图片准备

搜索并下载一张滑稽,用 windows 自带的画图软件打开,然后另存为某种 bmp 格式(如下图),这里代码实现了 24位 bmp 和 16色 bmp 格式图片的读取。
在这里插入图片描述

3-2. 头文件 BmpFormat.h 对 bmp 位图四个结构的定义:

#ifndef BMPFORMAT_H
#define BMPFORMAT_H

typedef unsigned char   BYTE;
typedef unsigned short  WORD;
typedef unsigned int    DWORD;
typedef long            LONG;
/* 1. 位图文件头 */
typedef struct tagBITMAPFILEHEADER
{
    //WORD    bfType;         // windows中为 BM (0x4d42)
    DWORD   bfSize;         // 文件大小
    WORD    bfReserved1;    // 保留,设置为 0
    WORD    bfReserved2;    // 保留,设置为 0
    DWORD   bfOffBits;      // 实际图像数据的偏移量
}BITMAPFILEHEADER;
/* 2. 位图信息头 */
typedef struct tagBITMAPINFOHEADER
{
    DWORD   biSize;         // 该结构所占字节数,windows下为 40
    LONG    biWidth;        // 图像宽度
    LONG    biHeight;       // 图像高度
    WORD    biPlanes;       // 位面数,1
    WORD    biBitCount;     // 单像素位数
    DWORD   biCompression;  // 压缩说明
    DWORD   biSizeImage;    // 图像大小,如无压缩,可设置为 0 
    LONG    biXPelsPerMerer;// 水平分辨率
    LONG    biYPelsPerMerer;// 垂直分辨率
    DWORD   biClrUsed;      // 位图使用的颜色数, 0 表示使用了全部颜色
    DWORD   biClrImportant; // 重要颜色数目
}BITMAPINFOHEADER;
/* 3. 调色板 */
typedef struct tagRGBQUAD
{
    BYTE    rgbBlue;        // 指定蓝色强度
    BYTE    rgbGreen;       // 指定绿色强度
    BYTE    rgbRed;         // 指定红色强度
    BYTE    rgbReserved;    // 保留,设为 0
}RGBQUAD;
/* 4. 位图数据 
* 扫描行由低向上存储,第一字节为左下角像素,最后一个字节为右上角像素
*/

#endif

这里注意将第一部分图片文件头结构体中 bfType 单独拎出来,是处于结构体需要对齐的考虑,并且注意读取时是按字节读取,且 pc 采用小端头存储,所以 bfType 读取出来后是 MB,而在一个字节内部,读取出来的顺序就是存储时的顺序,即高位对应前面的数据,低位对应后面的数据,这里在读取 16色 bmp 图片时有体现。

3-3. 读入 opencv 的 Mat 中并显示

#include <iostream>
#include <fstream>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/core/core.hpp>
#include "BmpFormat.h"
using namespace std;
using namespace cv;

/* 输出 bmp 图片头部信息 */
void bmpFileInfo(ifstream &fpbmp, int &Offset, int &rows, int &cols);
/* 将 bmp24bit 图片读入 Mat 中 */
void bmp24bitToMat(ifstream& fpbmp, Mat& bmp, int Offset);
/* 将 bmp16色 图片读入 Mat 中 */
void bmp16ToMat(ifstream& fpbmp, Mat& bmp, int Offset);

int main(int argc, char* argv[]) {
	/* 打开 bmp 文件*/
	ifstream fpbmp(".\\pic\\ppx16.bmp", ifstream::in | ifstream::binary);
	fpbmp.seekg(0, fpbmp.end);
	int length = fpbmp.tellg();
	fpbmp.seekg(0, fpbmp.beg);

	int Offset, rows, cols;
	bmpFileInfo(fpbmp, Offset, rows, cols); 
	Mat bmp(rows, cols, CV_8UC3);
	/* 24 位 bmp 图片 */
	//bmp24bitToMat(fpbmp, bmp, Offset);
	/* 16 色 bmp 图片 */
	bmp16ToMat(fpbmp, bmp, Offset);

	namedWindow("ppx", WINDOW_AUTOSIZE);
	imshow("ppx", bmp);
	waitKey(0);
	destroyWindow("ppx");

	return 0;
}

/* 输出 bmp 图片头部信息 */
void bmpFileInfo(ifstream &fpbmp, int& Offset, int& rows, int& cols) {
	BITMAPFILEHEADER fh;
	WORD bfType;
	fpbmp.read((char *)&bfType, sizeof(WORD));
	fpbmp.read((char *)&fh, sizeof(BITMAPFILEHEADER));

	if (bfType != 'MB') {
		cout << "It's not a bmp file." << endl;
		exit(1);
	}

	cout << "****** tagBITMAPFILEHEADER info *******" << endl;
	cout << " bfType			:" << " BM" << endl;
	cout << " bfSize			: " << (fh.bfSize) << endl;
	cout << " bfReserved1		: " << (fh.bfReserved1) << endl;
	cout << " bfReserved2		: " << (fh.bfReserved2) << endl;
	cout << " bfOffBits		: " << (fh.bfOffBits) << endl;
	cout << " 总字节偏移 :" << fpbmp.tellg() << endl << endl;

	BITMAPINFOHEADER fih;
	fpbmp.read((char*)&fih, sizeof(BITMAPINFOHEADER));
	cout << "****** tagBITMAPINFOHEADER info *******" << endl;
	cout << " 位图信息头字节数	: " << (fih.biSize) << endl;
	cout << " 图像像素宽度		: " << (fih.biWidth) << endl;
	cout << " 图像像素高度		: " << (fih.biHeight) << endl;
	cout << " 位面数			: " << (fih.biPlanes) << endl;
	cout << " 单像素位数		: " << (fih.biBitCount) << endl;
	cout << " 压缩说明		: " << (fih.biCompression) << endl;
	cout << " 图像大小		: " << (fih.biSizeImage) << endl;
	cout << " 水平分辨率		: " << (fih.biXPelsPerMerer) << endl;
	cout << " 垂直分辨率		: " << (fih.biYPelsPerMerer) << endl;
	cout << " 位图使用的颜色数	: " << (fih.biClrUsed) << endl;
	cout << " 重要颜色数目		: " << (fih.biClrImportant) << endl;
	cout << " 总字节偏移 :" << fpbmp.tellg() << endl << endl;

	Offset = fh.bfOffBits;
	rows = fih.biHeight;
	cols = fih.biWidth;

	if (Offset != 54) {
		RGBQUAD rgbPalette[16];
		fpbmp.read((char*)&rgbPalette, 16 * sizeof(RGBQUAD));
		cout << "****** tagRGBQUAD info *******" << endl;
		cout << "	(R, G, B, Alpha)" << endl;
		for (int i = 0; i < 16; i++) {
			cout << " No." << i << "	" << "(" \
				<< (rgbPalette[i].rgbRed + 0) << ", " \
				<< (rgbPalette[i].rgbGreen + 0) << ", " \
				<< (rgbPalette[i].rgbBlue + 0) << ", " \
				<< (rgbPalette[i].rgbReserved + 0) << ")" << endl;
			//if (i != 0 && i % 4 == 0) cout << endl;
		}
		cout << " 总字节偏移 :" << fpbmp.tellg() << endl;
	}
}
/* 将 bmp24bit 图片读入 Mat 中 */
void bmp24bitToMat(ifstream& fpbmp, Mat& bmp, int Offset) {
	fpbmp.seekg(Offset, fpbmp.beg);
	for (int i = (bmp.rows-1); i >=0 ; i--) {
		for (int j = 0; j < bmp.cols; j++) {
			fpbmp.read((char*)&bmp.at<Vec3b>(i, j)[0], 1);
			fpbmp.read((char*)&bmp.at<Vec3b>(i, j)[1], 1);
			fpbmp.read((char*)&bmp.at<Vec3b>(i, j)[2], 1);
		}
	}
}
/* 将 bmp16色 图片读入 Mat 中 */
void bmp16ToMat(ifstream& fpbmp, Mat& bmp, int Offset) {
	RGBQUAD rgbPalette[16];
	fpbmp.seekg(54, fpbmp.beg);
	fpbmp.read((char*)&rgbPalette, 16 * sizeof(RGBQUAD));
	fpbmp.seekg(Offset, fpbmp.beg);
	char tmp;
	for (int i = (bmp.rows - 1); i >= 0; i--) {
		for (int j = 0; j < bmp.cols; j+=2) {
			fpbmp.read((char*)&tmp, 1);
			bmp.at<Vec3b>(i, j)[0] = rgbPalette[tmp >> 4 & 0x0f].rgbBlue;
			bmp.at<Vec3b>(i, j)[1] = rgbPalette[tmp >> 4 & 0x0f].rgbGreen;
			bmp.at<Vec3b>(i, j)[2] = rgbPalette[tmp >> 4 & 0x0f].rgbRed;
			bmp.at<Vec3b>(i, j+1)[0] = rgbPalette[tmp & 0x0f].rgbBlue;
			bmp.at<Vec3b>(i, j+1)[1] = rgbPalette[tmp & 0x0f].rgbGreen;
			bmp.at<Vec3b>(i, j+1)[2] = rgbPalette[tmp & 0x0f].rgbRed;
		}
	}
}

注意,opencv 和 bmp 图片的通道顺序都是反序,即 BGR。

3-4. 结果分析

(1). 24 位 bmp 结果:

24位bmp
因为是真彩色图片,这里文件头偏移固定为 54 字节,因为没有调色板,两个保留字为 0,总大小的计算为: 50 4 2 × 3 + 54 = 762102 504^{2}\times 3 + 54=762102 ,位面数固定为 1,压缩说明为 0,指未压缩,图像大小在无压缩情况下也为 0,位图使用的颜色数为 0,指所有颜色都有用到,共 2 24 2^{24} 种,重要颜色数为 0,表示都重要。

(2). 16 色 bmp 结果:

16bmp
Alpha 是保留字节,设置为 0,总头部偏移: 54 + 4 × 16 = 118 54+4\times 16=118 ,图像大小: 252 × 504 × = 127008 252\times 504 \times =127008 ,这里因为只有 16 色,故一个字节刚好可以存储两个索引,如果是 256 色,则一个字节只能存储一个索引。
关于使用调色板的图片压缩,可以参考bmp图像压缩算法详细解析4

参考文章


  1. BMP文件结构 ↩︎

  2. BMP位图与调色板分析 ↩︎

  3. BMP图像结构 ↩︎

    扫描二维码关注公众号,回复: 11004522 查看本文章
  4. bmp图像压缩算法详细解析 ↩︎

发布了7 篇原创文章 · 获赞 1 · 访问量 446

猜你喜欢

转载自blog.csdn.net/m0_46318517/article/details/104425450