把opencv Mat 按位存成bmp二值图像 (1bit 1pixel)(位深度为1)

在 OpenCV 中,直接使用 imwrite 保存二值图像为 BMP 时,默认会将其存储为 8 位灰度图像(每个像素占用 1 字节)。要实现 1 位(位深度为1)的 BMP 二值图像,需要手动处理二进制数据并直接构造符合 BMP 文件格式规范的二进制文件。以下是分步解决方案:


步骤 1:将 OpenCV Mat 转换为二值数据

确保输入图像为单通道(CV_8UC1),且像素值仅为 0(黑)255(白)

cv::Mat binaryMat = ...; // 输入的二值矩阵(0和255)

// 转换为 0和1 的二值数据(按位存储)
cv::Mat bitMat;
binaryMat.convertTo(bitMat, CV_8UC1); // 确保数据类型为单通道
bitMat = (bitMat == 255);             // 将255映射为1,0保持为0

步骤 2:构造 BMP 文件结构

BMP 文件格式包含以下关键部分(按顺序写入):

  1. BMP文件头(BITMAPFILEHEADER)
  2. BMP信息头(BITMAPINFOHEADER)
  3. 调色板(Palette,适用于1位图像)
  4. 位图数据(每像素1bit,行对齐到4字节)
(1) 定义 BMP 头结构
#pragma pack(push, 1) // 禁用内存对齐
struct BITMAPFILEHEADER {
    uint16_t bfType;        // 文件类型,必须为 "BM" (0x4D42)
    uint32_t bfSize;        // 文件总大小
    uint16_t bfReserved1;   // 保留字段,设为0
    uint16_t bfReserved2;   // 保留字段,设为0
    uint32_t bfOffBits;     // 数据偏移量(头+调色板)
};

struct BITMAPINFOHEADER {
    uint32_t biSize;        // 信息头大小(40字节)
    int32_t  biWidth;       // 图像宽度(像素)
    int32_t  biHeight;      // 图像高度(像素)
    uint16_t biPlanes;      // 颜色平面数,必须为1
    uint16_t biBitCount;    // 每像素位数(1表示1位)
    uint32_t biCompression; // 压缩方式(0表示不压缩)
    uint32_t biSizeImage;   // 图像数据大小(行对齐后的字节数)
    int32_t  biXPelsPerMeter; 
    int32_t  biYPelsPerMeter;
    uint32_t biClrUsed;   
    uint32_t biClrImportant;
};
#pragma pack(pop)
(2) 调色板(Palette)

1位 BMP 需要定义 2 个调色板项(对应黑、白):

uint8_t palette[8] = {
    0,0,0,0,       // 颜色0:BGR黑 (00 00 00)
    255,255,255,0  // 颜色1:BGR白 (FF FF FF)
};

步骤 3:计算行对齐和图像数据大小

BMP 要求每行数据对齐到 4 字节:

int width = binaryMat.cols;
int height = binaryMat.rows;

// 每行像素所需的字节数(按1位存储)
int rowSize = (width + 31) / 32 * 4; // (位宽对齐到32bits,再转为字节数并4字节对齐)
int dataSize = rowSize * height;

步骤 4:生成 BMP 二进制数据

将二值数据按行打包为每像素1位:

std::vector<uint8_t> bitmapData(dataSize, 0); // 初始化全0

for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
        // 获取二值数据(0或1)
        uint8_t bit = bitMat.at<uint8_t>(height - y - 1, x); // BMP行存储是自下而上的

        // 计算目标位置的字节和位偏移
        int byteIdx = y * rowSize + x / 8;
        int bitIdx = 7 - (x % 8); // BMP位顺序是高位在前

        // 设置对应位
        if (bit) {
            bitmapData[byteIdx] |= (1 << bitIdx);
        }
    }
}

步骤 5:填充 BMP 头信息

BITMAPFILEHEADER bmfh = {
    0x4D42,                             // bfType: 'BM'
    sizeof(BITMAPFILEHEADER) + 
    sizeof(BITMAPINFOHEADER) + 
    sizeof(palette) + dataSize,         // bfSize: 总文件大小
    0,                                  // 保留字段
    0,
    sizeof(BITMAPFILEHEADER) + 
    sizeof(BITMAPINFOHEADER) + 
    sizeof(palette)                     // bfOffBits: 数据偏移量
};

BITMAPINFOHEADER bmih = {
    sizeof(BITMAPINFOHEADER),           // biSize
    width,                              // biWidth
    height,                             // biHeight
    1,                                  // biPlanes
    1,                                  // biBitCount(1位)
    0,                                  // biCompression(不压缩)
    dataSize,                           // biSizeImage(数据大小)
    0,                                  // biXPelsPerMeter
    0,                                  // biYPelsPerMeter
    0,                                  // biClrUsed
    0
};

步骤 6:写入文件

将头、调色板、位图数据写入文件:

std::ofstream outFile("output_1bit.bmp", std::ios::binary");

outFile.write((char*)&bmfh, sizeof(BITMAPFILEHEADER));
outFile.write((char*)&bmih, sizeof(BITMAPINFOHEADER));
outFile.write((char*)palette, sizeof(palette));
outFile.write((char*)bitmapData.data(), bitmapData.size());

outFile.close();

完整代码示例

#include <opencv2/opencv.hpp>
#include <fstream>
#include <vector>

#pragma pack(push, 1)
struct BITMAPFILEHEADER { /* 同上 */ };
struct BITMAPINFOHEADER { /* 同上 */ };
#pragma pack(pop)

void save1bitBMP(const cv::Mat& binaryMat, const std::string& filename) {
    int width = binaryMat.cols;
    int height = binaryMat.rows;

    // ---- 生成调色板 ----
    uint8_t palette[8] = {0,0,0,0, 255,255,255,0};

    // ---- 计算行对齐和数据大小 ----
    int rowSize = ((width + 31) / 32) * 4;
    int dataSize = rowSize * height;

    // ---- 将 OpenCV Mat 转换为 1bit 数据 ----
    cv::Mat bitMat;
    binaryMat.convertTo(bitMat, CV_8UC1);
    bitMat = (bitMat == 255);

    std::vector<uint8_t> bitmapData(dataSize, 0);
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            uint8_t bit = bitMat.at<uint8_t>(height - y - 1, x); // BMP存储行倒序
            int byteIdx = y * rowSize + x / 8;
            int bitIdx = 7 - (x % 8);
            if (bit) {
                bitmapData[byteIdx] |= (1 << bitIdx);
            }
        }
    }

    // ---- 填充 BMP 头信息 ----
    BITMAPFILEHEADER bmfh = {/* 同步骤5 */};
    BITMAPINFOHEADER bmih = {/* 同步骤5 */};

    // ---- 写入文件 ----
    std::ofstream outFile(filename, std::ios::binary");
    outFile.write((char*)&bmfh, sizeof(BITMAPFILEHEADER));
    outFile.write((char*)&bmih, sizeof(BITMAPINFOHEADER));
    outFile.write((char*)palette, sizeof(palette));
    outFile.write((char*)bitmapData.data(), bitmapData.size());
}

// 使用示例
int main() {
    cv::Mat mat = cv::imread("input.png", cv::IMREAD_GRAYSCALE);
    cv::threshold(mat, mat, 128, 255, cv::THRESH_BINARY);
    save1bitBMP(mat, "output_1bit.bmp");
    return 0;
}

关键注意事项

  1. BMP 行顺序:BMP 默认存储从下到上的行数据,需翻转行索引。
  2. 位操作:每个字节中,像素的位存储顺序是高位在前(左到右的第1个像素对应字节的最高位 bit7)。
  3. 调色板定义:1位的 BMP 必须包含 2 种颜色,分别对应像素值的 0 和 1。
  4. 内存对齐:使用 #pragma pack(1) 确保结构中无填充字节。

通过此方法,可以将 OpenCV 的二值 Mat 保存为真正的 1 位 BMP 图像。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

X-Vision

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值