Exercise3.5--封装图片操作

三个练习过后,我感觉,最基础的图片操作,载入,写出,画直方图这些操作千篇一律,是时候将他们封装在一起了


封装的东西大致分为两类,图片的加载与输出以及直方图的建立与输出。

于是我将所有的操作都置于namespace ImageUtil

基本类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//色彩
typedef struct ImageColor{
BYTE r, g, b, a;
}RGBA;

//图片的信息
typedef struct ImageData
{
BITMAPFILEHEADER fileHeader;
BITMAPINFOHEADER infoHeader;
RGBQUAD rgbquad[256];
BYTE * pImg;
int length;
int width, height;

ImageData& operator+(ImageData& d0);
ImageData& operator*(float k);

}IMGDATA;

//直方图的信息
typedef struct GrayHistogram
{
double gray[256] = { 0 };
int pixelCount = 0;
void normalize();

private:
bool isNormalize = false;
}GRAYHISTOGRAM;

读写操作

在这里,我将读写操作从Exercise1当中的fwrite,fread改为了cpp当中的iofstream,个人感觉更加方便

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
//读取图片
ImageData ImageUtil::loadImage(const std::string& path)
{
std::ifstream ifstream;
ifstream.open(path, std::ios::binary);
if (!ifstream.is_open())
return {};

BITMAPFILEHEADER fileHeader;
BITMAPINFOHEADER infoHeader;
RGBQUAD rgbquad[256];

ifstream.read(reinterpret_cast<char *>(&fileHeader), sizeof(BITMAPFILEHEADER));
ifstream.read(reinterpret_cast<char *>(&infoHeader), sizeof(BITMAPINFOHEADER));
ifstream.read(reinterpret_cast<char *>(&rgbquad), sizeof(RGBQUAD) * infoHeader.biClrUsed);

BYTE *img = new BYTE[infoHeader.biSizeImage];
ifstream.read(reinterpret_cast<char*>(img), infoHeader.biSizeImage);

IMGDATA imgdate;
imgdate.infoHeader = infoHeader;
imgdate.fileHeader = fileHeader;
for (int i = 0; i < 256; i++)
{
imgdate.rgbquad[i] = rgbquad[i];
}

BYTE *imgWithoutError = new BYTE[(infoHeader.biWidth * infoHeader.biBitCount / 8) * infoHeader.biHeight];
//int byteWidth = (infoHeader.biWidth * (infoHeader.biClrUsed / 8) + 3) / 4 * 4;
int point = -1;
for(int i = 0;i < infoHeader.biHeight;i++)
{
for(int j = 0;j < (infoHeader.biWidth * infoHeader.biBitCount / 8);j++)
{
imgWithoutError[i * (infoHeader.biWidth * infoHeader.biBitCount / 8) + j] = img[++point];
}

//索引值与真实值的区别
while ((point + 1) % 4 != 0)
point++;
}

delete[] img;

imgdate.pImg = imgWithoutError;
imgdate.length = infoHeader.biSizeImage;
imgdate.width = infoHeader.biWidth;
imgdate.height = infoHeader.biHeight;


ifstream.close();
return imgdate;
}

//输出图片
void ImageUtil::outputImage(ImageData data, const int clrUsed, const std::string& path)
{
std::ofstream out;
out.open(path, std::ios::out | std::ios::trunc | std::ios::binary);
if (!out.is_open())
return;

BYTE *img = new BYTE[data.infoHeader.biSizeImage];
int byteWidth = (infoHeader.biWidth * infoHeader.biBitCount / 8);
int point = -1;
for(int i = 0;i < data.height;i++)
{
for(int j = 0;j < byteWidth;j++)
{
img[++point] = data.pImg[i * byteWidth + j];
}

//点在数组当中的索引值与宽度正好相差1
while ((point + 1) % 4 != 0)
img[++point] = 0;
}

std::cout << "output " << path << "...." << std::endl;
out.write(reinterpret_cast<char *>(&data.fileHeader), sizeof(BITMAPFILEHEADER));
out.write(reinterpret_cast<char *>(&data.infoHeader), sizeof(BITMAPINFOHEADER));
out.write(reinterpret_cast<char *>(&data.rgbquad), clrUsed * sizeof(RGBQUAD));
out.write(reinterpret_cast<char *>(img), data.infoHeader.biSizeImage);
out.close();

delete[] img;
}

//得到图片的直方图信息
GRAYHISTOGRAM ImageUtil::getHistogram(const IMGDATA data)
{
GRAYHISTOGRAM grayhistogram;
int point = 0;
for (int i = 0; i < data.height; i++)
{
for (int j = 0; j < data.width; j++)
{
grayhistogram.gray[data.pImg[point++]]++;
}

while (point % 4 != 0)
point++;
}

grayhistogram.pixelCount = data.width * data.height;
return grayhistogram;
}

//输出直方图
void ImageUtil::outputHistogram(const IMGDATA data, const std::string& path)
{
IMGDATA newData = data;
GRAYHISTOGRAM histogram = ImageUtil::getHistogram(data);

// newData.fileHeader.bfType = 0x4d42;
// newData.fileHeader.bfReserved1 = 0;
// newData.fileHeader.bfReserved2 = 0;
newData.fileHeader.bfSize = sizeof(BITMAPINFOHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 2 + 256 * 256;
newData.fileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 2;
//
// newData.infoHeader.biSize = sizeof(BITMAPINFOHEADER);
// newData.infoHeader.biPlanes = 1;
newData.infoHeader.biBitCount = 8;
newData.infoHeader.biClrUsed = 2;
//newData.infoHeader.biCompression = BI_RGB;
newData.infoHeader.biSizeImage = 256 * 256;
newData.infoHeader.biHeight = 256;
newData.infoHeader.biWidth = 256;
// newData.infoHeader.biClrImportant = data.infoHeader.biClrImportant;
// newData.infoHeader.biXPelsPerMeter = data.infoHeader.biXPelsPerMeter;
// newData.infoHeader.biYPelsPerMeter = data.infoHeader.biYPelsPerMeter;

newData.pImg = new BYTE[256 * 256];
for(int i = 0;i < 256 * 256;i++)
{
newData.pImg[i] = 0;
}

histogram.normalize();

RGBQUAD white;
white.rgbReserved = 0;
white.rgbRed = 255;
white.rgbBlue = 255;
white.rgbGreen = 255;

RGBQUAD black;
black.rgbReserved = 0;
black.rgbRed = 0;
black.rgbBlue = 0;
black.rgbGreen = 0;

newData.rgbquad[0] = black;
newData.rgbquad[1] = white;

for (int i = 0; i < 256; i++)
{
int length = histogram.gray[i] * 255 * 20;
if (length > 255)
length = 255;
for (int j = 0; j < length; j++)
{
newData.pImg[j * 256 + i] = 1;
}
}

newData.length = 256 * 256;
newData.width = 256;
newData.height = 256;

outputImage(newData, 2, path);
}

由于和之前Exercise1Exercise2的操作是一样的,因此也就不再赘述了。

转灰度图操作

而由于大部分的图片处理都是基于灰度图的,因此这里也封装了将24/32位图片输入然后直接输出8位图的操作,以便于之后的练习(这样可以直接输入24/32位图,而不是先使用Exercise1的代码操作改为8位图再使用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
ImageUtil::ImageData ImageUtil::loadImageToGray(const std::string & path)
{
ImageData data = loadImage(path);
if (data.infoHeader.biBitCount != 8)
{
RGBA *rgba = new RGBA[data.width * data.height];
switch (static_cast<int>(data.infoHeader.biBitCount))
{
case 16:
std::cout << "无法转换16位图(太麻烦,懒癌发作)" << std::endl;
break;
case 24:
{
int point = 0;
for (int i = 0; i < data.height; i++)
{
for (int j = 0; j < data.width; j++)
{
rgba[i * data.width + j].b = data.pImg[point++];
rgba[i * data.width + j].g = data.pImg[point++];
rgba[i * data.width + j].r = data.pImg[point++];
}
}
data.infoHeader.biBitCount = 8;
data.infoHeader.biClrUsed = 256;

data.fileHeader.bfOffBits = 54 + 4 * 256;
int byteLine = (data.width * data.infoHeader.biBitCount / 8 + 3) / 4 * 4;
data.infoHeader.biSizeImage = byteLine * data.height;
data.fileHeader.bfSize = 54 + byteLine * data.height + 4 * 256;

for (int i = 0; i < 256; i++)
{
data.rgbquad[i].rgbRed = i;
data.rgbquad[i].rgbGreen = i;
data.rgbquad[i].rgbBlue = i;
data.rgbquad[i].rgbReserved = 0;

}

BYTE * newData = new BYTE[data.width * data.height];
point = 0;
for (int i = 0; i < data.height; i++)
{
for (int j = 0; j < data.width; j++)
{
newData[point++] = rgba[i * data.width + j].r * 0.299 + rgba[i * data.width + j].g * 0.587 + rgba[i * data.width + j].b * 0.114;
}
}

delete[] data.pImg;
data.pImg = newData;
break;
}
case 32:
{
int point = 0;
for (int i = 0; i < data.height; i++)
{
for (int j = 0; j < data.width; j++)
{
rgba[i * data.width + j].b = data.pImg[point++];
rgba[i * data.width + j].g = data.pImg[point++];
rgba[i * data.width + j].r = data.pImg[point++];
rgba[i * data.width + j].a = data.pImg[point++];
}
}
data.infoHeader.biBitCount = 8;
data.infoHeader.biClrUsed = 256;

data.fileHeader.bfOffBits = 54 + 4 * 256;
int byteLine = (data.width * data.infoHeader.biBitCount / 8 + 3) / 4 * 4;
data.infoHeader.biSizeImage = byteLine * data.height;
data.fileHeader.bfSize = 54 + byteLine * data.height + 4 * 256;

for (int i = 0; i < 256; i++)
{
data.rgbquad[i].rgbRed = i;
data.rgbquad[i].rgbGreen = i;
data.rgbquad[i].rgbBlue = i;
data.rgbquad[i].rgbReserved = 0;

}

BYTE * newData = new BYTE[data.width * data.height];
point = 0;
for (int i = 0; i < data.height; i++)
{
for (int j = 0; j < data.width; j++)
{
newData[point++] = rgba[i * data.width + j].r * 0.299 + rgba[i * data.width + j].g * 0.587 + rgba[i * data.width + j].b * 0.114;
}
}

delete[] data.pImg;
data.pImg = newData;
break;
}
default:
break;
}
delete[] rgba;
}

return data;
}

其他封装

其实也就是一些比较常用的数学操作,目前我封装了3个,分别是对灰度值的约束函数clamp以及ImageData的加与乘操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
int ImageUtil::clamp(const int c)
{
if (c > 255)
return 255;
if (c < 0)
return 0;
return c;
}

//加
ImageUtil::ImageData & ImageUtil::ImageData::operator+(ImageData& d0)
{
for(int i = 0;i < height;i++)
{
for(int j = 0;j < width;j++)
{
pImg[i * width + j] = ImageUtil::clamp(pImg[i * width + j] + d0.pImg[i * width + j]);
}
}

return *this;

}

//乘
ImageUtil::ImageData & ImageUtil::ImageData::operator*(const float k)
{
for(int i =0;i < height;i++)
{
for(int j = 0;j < width;j++)
{
pImg[i * width + j] = ImageUtil::clamp(pImg[i * width + j] * k);
}
}

return *this;
}

至于还有没有后续的封装就看后面的练习所总结了。

源码

https://github.com/DearSummer/DigitalImageProcessing

  • 本文作者: ShinyGX
  • 本文链接: https://ShinyGX.github.io/posts/d7efe202/
  • 版权声明: 本博客所有文章除特别声明外,均采用 https://creativecommons.org/licenses/by-nc-sa/3.0/ 许可协议。转载请注明出处!
0%