前言
本文主要介绍了将BMP图像转换为字符串画的一般过程,包括图像的缩放、灰度化和C++读取处理等步骤。
BMP文件格式简介
BMP(Bitmap-File)图形文件,几乎所有图像处理软件都支持BMP图像文件格式,是非常见一种图像格式,通常文件体积较大。
BMP位图文件由4个部分组成:位图文件头(bitmap-file header)、位图信息头(bitmap-information header)、彩色表(color table)和定义位图的字节(位图数据,即图像数据,Data Bits 或Data Body)阵列,它具有如下所示的形式。
在Windows中具体的结构定义,可以包含头文件#include <Windows.h>,可以查看详细信息。
文件头(共计14字节):
typedef struct tagBITMAPFILEHEADER {
WORD bfType; //2字节,标记,通常为BM, 十六进制为0x4D42
DWORD bfSize; //4字节,文件大小
WORD bfReserved1; //2字节,保留
WORD bfReserved2; //2字节,保留
DWORD bfOffBits; //4字节,图像数据在文件中的偏移量
} BITMAPFILEHEADER, FAR *LPBITMAPFILEHEADER, *PBITMAPFILEHEADER;
信息头(共计40字节):
typedef struct tagBITMAPINFOHEADER{
DWORD biSize; //4字节,
LONG biWidth; //4字节,图像宽度,以像素为单位
LONG biHeight; //4字节,图像高度,以像素为单位
WORD biPlanes; //2字节
WORD biBitCount; //2字节,位深, 1、4、8、16、24、32
DWORD biCompression; //4字节,压缩标识
DWORD biSizeImage; //4字节,位图数据的大小,必须是4的倍数
LONG biXPelsPerMeter; //4字节,像素/米表示的水平分辨率
LONG biYPelsPerMeter; //4字节,像素/米表示的垂直分辨率
DWORD biClrUsed; //4字节,位图使用的颜色数
DWORD biClrImportant; //4字节,重要的颜色数
} BITMAPINFOHEADER, FAR *LPBITMAPINFOHEADER, *PBITMAPINFOHEADER;
图像缩放算法
转为字符画时,图像的宽度太宽时在控制台显示时,会自动换行,就看不出图像的大致样子,所以在显示处理之前先把图像缩小一下,常见的的缩放算法有邻域插值、双线性插值、双三次插值,本文使用邻域插值算法,因为比较简单容易处理。
图像灰度化
像灰度化是将一幅彩色图像转换为灰度化图像的过程。彩色图像通常包括R、G、B三个分量,分别显示出红绿蓝等各种颜色,灰度化就是使彩色图像的R、G、B三个分量相等的过程。灰度图像中每个像素仅具有一种样本颜色,其灰度是位于黑色与白色之间的多级色彩深度,灰度值大的像素点比较亮,反之比较暗,像素值最大为255(表示白色),像素值最小为0(表示黑色)。
一种常见的方法是将RGB三个分量求和再取平均值,但更为准确的方法是设置不同的权重,将RGB分量按不同的比例进行灰度划分。比如人类的眼睛感官蓝色的敏感度最低,敏感最高的是绿色,因此将RGB按照0.299、0.587、0.144比例加权平均能得到较合理的灰度图像,公式如下所示。
G
r
a
y
=
R
∗
0.299
+
G
∗
0.587
+
B
∗
0.114
Gray = R * 0.299 + G * 0.587 + B * 0.114
Gray=R∗0.299+G∗0.587+B∗0.114
示例代码
#include <iostream>
#include <fstream>
#include <Windows.h>
using namespace std;
//字符表,可以自行调整
string table = "#8XOHLTI)i=+;:,. ";
/*
* data 图像数据
* srcWidth 图像宽度
* srcHeight 图像高度
* ratio 缩放比例
*/
void output(const char *data, int srcWidth, int srcHeight, double ratio)
{
int width = int(double(srcWidth) * ratio); //缩放后的宽度
int height = int(double(srcHeight) * ratio); //缩放后的高度
int lineBytes = (srcWidth * 24 + 31) / 32 * 4; //原始图像每行字节数
int rowSize = (width * 24 + 31) / 32 * 4; //缩放后每行字节数
double unit = 257.0 / double(table.size());
for (int i = height - 1; i >= 0; --i) {
int x = (int)(double(i) / ratio);//原始行
for (int j = 0; j < width; ++j) {
int y = (int)(double(j) / ratio);//原始列
if (x >= 0 && x < srcHeight && y >= 0 && y < srcHeight) {
const uint8_t* p = (const uint8_t*)(data + x * lineBytes + y * 3);
//灰度化
int gray = (unsigned int)((float)p[2] * 0.299 +
(float)p[1] * 0.587 +
(float)p[0] * 0.114);
//映射到对应的字符
int idx = int(gray / unit);
cout << table[idx];
}
}
cout << "\n";
}
}
int main()
{
const char* filename = "1.bmp";
ifstream file(filename, ios::binary);
if (!file.is_open()) {
return 0;
}
BITMAPFILEHEADER header = { 0 };
BITMAPINFOHEADER info = { 0 };
file.read((char*)&header, sizeof(header));
file.read((char*)&info, sizeof(info));
if (header.bfType != 0x4D42 || info.biBitCount != 24) {
cout << "不支持" << endl;
return 0;
}
char* buffer = new char[info.biSizeImage];
file.read(buffer, info.biSizeImage);
output(buffer, info.biWidth, info.biHeight, 0.4);
delete[] buffer;
return 0;
}