C++读取bmp格式图片
类的格式按照bmp的文件头、信息头、调色板和数据域写好即可
因为图片是按字节读的,所以要强制让编译器不对结构体进行字节对齐,否则会出错
因为bmp格式规定存储时每行的像素数要补齐至4的整数倍,所以读入和写出时要加一些对应的操作
——————————————————————————————————————————————
2019.9.6更新
增加了边缘检测、均值滤波和中值滤波
详情见注释
#include <bits/stdc++.h>
#pragma GCC optimize(3)
using namespace std;
typedef unsigned short w;
typedef unsigned int dw;
class bmp
{
#pragma pack(1)//取消编译器的字节对齐
struct FileHead
{
w type;
dw size;
w reserved1, reserved2;
dw offset;
} fileHead{};
#pragma pack(1)
struct InfoHead
{
dw size, width, height;
w planes; //图像的位面数
w bitNum; //每个像素的位数
dw compression; //压缩类型
dw imgSize; //单位为字节
dw xPelsPerMeter, yPelsPerMeter;
dw colorNum; //使用的色彩数
dw colorImportantNum; //重要的颜色数
} infoHead{};
#pragma pack(1)
struct Palette
{
w b, g, r, reversed;
} palette{};
struct Sum;
#pragma pack(1)
struct Pixel
{
unsigned char b, g, r;//24位位图按照BGR的顺序存放, 32位BGRA
// friend ostream &operator<<(ostream &out, const Pixel a)
// {
// return out << '[' << (unsigned) a.b << ' ' << (unsigned) a.g << ' ' << (unsigned) a.r << ']';
// }
Pixel(unsigned char b = 0, unsigned char g = 0, unsigned char r = 0) : b(b), g(g), r(r)
{}
Pixel &operator=(const Sum &a)
{
b = (unsigned char) a.b;
g = (unsigned char) a.g;
r = (unsigned char) a.r;
}
bool operator<(const Pixel &a) const
{
if (b == a.b)
{
if (g == a.g)
return r < a.r;
return g < a.g;
}
return b < a.b;
}
};
struct Sum //用来维护二维前缀和的结构体
{ // 因为pixel的rgb都是1字节,求和会溢出,所以另开一个
int b, g, r;
Sum(int b = 0, int g = 0, int r = 0) : b(b), g(g), r(r)
{}
template<typename T>
//既可以+=Sum, 也可以+=Pixel
Sum &operator+=(const T &a)
{
b += a.b;
g += a.g;
r += a.r;
return *this;
}
Sum operator+(const Sum &a)
{
return {b + a.b, g + a.g, r + a.r};
}
Sum operator-(const Sum &a)
{
return {b - a.b, g - a.g, r - a.r};
}
template<typename T>
Sum &operator-=(const T &a)
{
b -= a.b;
g -= a.g;
r -= a.r;
return *this;
}
Sum &operator/=(const int k)
{
b /= k;
g /= k;
r /= k;
}
};
inline int pos(int i, int j)
{
return i * realWidth + j;
}
ifstream in;
ofstream out;
int realWidth{};
public:
bmp() = default;
~bmp()
{
delete[] img;
}
unsigned int n{};
Pixel *img = nullptr;
bool open(const string &file)
{
in.open(file, ios::in | ios::binary);
if (!in)
return 0 & puts("无法打开文件");
return true;
}
void read()
{
if (!in)
{
puts("未打开文件");
return;
}
in.read((char *) &fileHead, sizeof(fileHead));
in.read((char *) &infoHead, sizeof(infoHead));
realWidth = ((infoHead.width - 1) / 4) * 4 + 4;
n = infoHead.height * realWidth;
img = new Pixel[infoHead.height * realWidth];
in.read((char *) img, sizeof(Pixel) * n);
in.close();
}
Pixel &operator[](int id)
{
return img[id];
}
const InfoHead &getInfoHead() const
{
return infoHead;
}
const FileHead &getFileHead() const
{
return fileHead;
}
void write(const string &file)
{
out.open(file, ios::out | ios::binary);
out.write((char *) &fileHead, sizeof(fileHead));
out.write((char *) &infoHead, sizeof(infoHead));
out.write((char *) img, sizeof(Pixel) * n);
out.close();
}
void toGrayImg()
{
unsigned char val;
for (int i = 0; i < n; ++i)
{
val = 0.587 * img[i].g + 0.299 * img[i].r + 0.114 * img[i].b;
img[i] = Pixel(val, val, val);//rgb都设为一样就是灰度图了
}
}
void meanFiltering(int k)//k是半径, 如k取2, 求均值的范围为3 * 3
{
--k;
//均值滤波, 对每个大小为(2 * k + 1) * (2 * k + 1)的子矩阵求和再平均,把中心的值改为均值。
Sum *dp = new Sum[realWidth * infoHead.height];//这里用了二维前缀和来加速
#define summ(x1, y1, x2, y2) (dp[pos(x2, y2)]+(x1 && y1 ? dp[pos(x1-1, y1-1)] : 0)-(x1 ? dp[pos(x1-1, y2)] : 0)-(y1 ? dp[pos(x2, y1-1)] : 0))
for (int i = 0; i < infoHead.height; ++i)//预处理二维前缀和
{
for (int j = 0; j < infoHead.width; ++j)
{
dp[pos(i, j)] += img[pos(i, j)];
if (i)
dp[pos(i, j)] += dp[pos(i - 1, j)];
if (j)
dp[pos(i, j)] += dp[pos(i, j - 1)];
if (i && j)
dp[pos(i, j)] -= dp[pos(i - 1, j - 1)];
}
}
Sum val;
for (int i = 0; i < infoHead.height; ++i)
{
for (int j = 0; j < infoHead.width; ++j)
{
if (i < k || j < k || i + k > infoHead.height - 1 || j + k > infoHead.width - 1) continue;
val = summ(i - k, j - k, i + k, j + k);
val /= (k * 2 + 1) * (k * 2 + 1);
img[pos(i, j)] = Pixel((unsigned char) val.b, (unsigned char) val.g, (unsigned char) val.r);
}
}
delete[] dp;
}
Pixel getMedian(int x, int y, int k)
{
vector<Pixel> a;
a.clear();
for (int i = x - k; i <= x + k; ++i)
for (int j = y - k; j <= y + k; ++j)
a.push_back(img[pos(i, j)]);
// nth_element(a.begin(), a.begin() + ((2 * k + 1) * (2 * k + 1)) / 2, a.end());
sort(a.begin(), a.end());
return a[((2 * k + 1) * (2 * k + 1)) / 2];
}
void solve()//增加噪声,用于检验中值滤波和均值滤波
{
for (int i = 0; i < infoHead.height; ++i)
{
for (int j = 0; j < infoHead.width; ++j)
{
if (rand() < 1000)
img[pos(i, j)] = Pixel(255, 255, 255);
}
}
}
void medianFiltering(int k)//k是半径
{//中值滤波,用子矩阵的中位数替换中心的值
Pixel *IMG = new Pixel[infoHead.height * ((infoHead.width - 1) / 4 * 4 + 4)];
--k;
for (int i = k; i + k < infoHead.height; ++i)
{
for (int j = k; j + k < infoHead.width; ++j)
{
IMG[pos(i, j)] = getMedian(i, j, k);
}
}
for (int i = k; i + k < infoHead.height; ++i)
{
for (int j = k; j + k < infoHead.width; ++j)
{
img[pos(i, j)] = IMG[pos(i, j)];
}
}
delete[]IMG;
}
void toEdge()//边缘检测
{//采用梯度下降法,对x方向和y方向分别求差分,每个像素处保存一个向量(x, y), 向量的模小于阈值的像素涂黑,其余涂白
int *difR = new int[realWidth * infoHead.height], *difC = new int[realWidth * infoHead.height];
for (int i = 1; i < infoHead.height; ++i)
{
for (int j = 0; j < infoHead.width; ++j)
{
difR[pos(i, j)] = img[pos(i, j)].b - img[pos(i - 1, j)].b;
}
}
for (int i = 0; i < infoHead.height; ++i)
{
for (int j = 1; j < infoHead.width; ++j)
{
difC[pos(i, j)] = img[pos(i, j)].b - img[pos(i, j - 1)].b;
}
}
for (int i = 0; i < n; ++i)
{
if (sqrt(difR[i] * difR[i] + difC[i] * difC[i]) < 6)
img[i] = Pixel(255, 255, 255);
else
img[i] = Pixel(0, 0, 0);
}
delete[] difC;
delete[] difR;
}
};
int main()
{
bmp pic;
pic.open("test.bmp");
pic.read();
// printf("%u %u\n", pic.getInfoHead().imgSize, pic.getInfoHead().width);
// pic.toGrayImg();
// pic.solve();
// pic.write("g01.bmp");
pic.toEdge();
// pic.meanFiltering(2);
pic.medianFiltering(2);
pic.write("test.bmp");
return 0;
}
原图
直接边缘检测的效果
中值滤波后的效果