【问题标题】:read pixel value in bmp file [closed]读取bmp文件中的像素值[关闭]
【发布时间】:2012-03-06 22:51:49
【问题描述】:

如何在 Windows 上以 C 或 C++ 读取 24 位 BMP 图像的所有像素 [h*w] 的颜色值[最好没有任何第三方库]。我得到了 Dev-C++
一个工作代码将不胜感激,因为我从未从事过图像阅读工作,并且在谷歌搜索后来到了 SO [如果你能比我更好地用谷歌搜索,请提供链接]。

【问题讨论】:

  • en.wikipedia.org/wiki/BMP_file_format

标签: c++ c image bmp dev-c++


【解决方案1】:

您必须先阅读位图标题。到达位图标题中的数据偏移量并逐行读取像素后,注意bmp文件格式的填充。

看看msdn http://msdn.microsoft.com/en-us/library/aa452883.aspx

http://msdn.microsoft.com/en-us/library/windows/desktop/dd318229(v=vs.85).aspx

【讨论】:

  • 我使用的是 C/C++ 而不是 VC++,谢谢您的帮助 :)
【解决方案2】:

以下代码 sn-p 并不完整,并且包含许多隐藏的假设和错误。我只是从零开始为一个大学课程项目编写的,它只是通过观察,它最低限度地满足了所有要求。我没有再研究它了,因为肯定有图书馆可以更好地完成这项工作。

以下是它可以正常工作的条件(cmets 中指出了一些假设):

  1. 它在 Windows 上运行,我不确定其他平台
  2. 适用于 24 位彩色 BMP 图像
  3. 它假定图像的宽度是 4 的倍数,所以它不处理填充字节以防万一
  4. 它将图像宽度和高度解码为 32 位小端整数
  5. 它返回一个指向动态分配内存的指针,如果调用者没有释放它可能会导致内存泄漏

其他答案已经涵盖了其中一些问题。


你可以试试这个:

unsigned char* readBMP(char* filename)
{
    int i;
    FILE* f = fopen(filename, "rb");
    unsigned char info[54];

    // read the 54-byte header
    fread(info, sizeof(unsigned char), 54, f); 

    // extract image height and width from header
    int width = *(int*)&info[18];
    int height = *(int*)&info[22];

    // allocate 3 bytes per pixel
    int size = 3 * width * height;
    unsigned char* data = new unsigned char[size];

    // read the rest of the data at once
    fread(data, sizeof(unsigned char), size, f); 
    fclose(f);

    for(i = 0; i < size; i += 3)
    {
            // flip the order of every 3 bytes
            unsigned char tmp = data[i];
            data[i] = data[i+2];
            data[i+2] = tmp;
    }

    return data;
}

现在data 应该包含像素的 (R, G, B) 值。像素 (i, j) 的颜色存储在data[3 * (i * width + j)]data[3 * (i * width + j) + 1]data[3 * (i * width + j) + 2]

在最后一部分中,第一个和第三个像素之间的交换完成了,因为我发现颜色值存储为 (B, G, R) 三元组,而不是 (R, G, B)。

【讨论】:

  • 如果您正在阅读 24 位彩色 BMP,您还需要注意行填充。由于某些原因,BMP 期望所有行以 4 的字节倍数对齐。您可以使用以下命令从图像宽度计算填充:int row_padded = (width*3 + 3) &amp; (~3) 然后fread() 一行row_padded 字节,但仅使用宽度元素。其余的被丢弃......
  • 请注意,上面的函数有一些不足,在图像的宽度和高度的分配上: 1.它假设小端。它不适用于 big endian 平台 2。它假定 sizeof(int) 为 4。如果不是,它将不起作用。
  • 这个答案有bug,更新请看我的评论stackoverflow.com/questions/9296059/…
【解决方案3】:

填充修复后readBMP函数代码:

unsigned char* ReadBMP(char* filename)
{
    int i;
    FILE* f = fopen(filename, "rb");

    if(f == NULL)
        throw "Argument Exception";

    unsigned char info[54];
    fread(info, sizeof(unsigned char), 54, f); // read the 54-byte header

    // extract image height and width from header
    int width = *(int*)&info[18];
    int height = *(int*)&info[22];

    cout << endl;
    cout << "  Name: " << filename << endl;
    cout << " Width: " << width << endl;
    cout << "Height: " << height << endl;

    int row_padded = (width*3 + 3) & (~3);
    unsigned char* data = new unsigned char[row_padded];
    unsigned char tmp;

    for(int i = 0; i < height; i++)
    {
        fread(data, sizeof(unsigned char), row_padded, f);
        for(int j = 0; j < width*3; j += 3)
        {
            // Convert (B, G, R) to (R, G, B)
            tmp = data[j];
            data[j] = data[j+2];
            data[j+2] = tmp;

            cout << "R: "<< (int)data[j] << " G: " << (int)data[j+1]<< " B: " << (int)data[j+2]<< endl;
        }
    }

    fclose(f);
    return data;
}

【讨论】:

  • @arc_lupus 他返回data。调用者完成后需要deletedata
  • 可能是错的,但我相信这段代码有错字。所需的内存量是 3 * width * height,而不是 row_padded。 row_padded 仅用于读取文件。
  • @JohnSmith 但他正在读取文件,所以填充没问题?因此,如果您的图像连续有 121 个像素,请准备读取 124 并丢弃最后 3 个像素——如果我理解正确的话。
  • @KrzysztofKachniarz @jiggunjer 据我了解,他一遍又一遍地将每一行存储在 data 数组中。当函数返回时,他将只有图像的最后一行,而不是整个图像。再一次,您不需要在内存中填充数组。为了存储您需要3*width*height 字符的图像,而不仅仅是3*width+somthing 字符
【解决方案4】:

我创建了一个适用于每像素 24 位的 bmp 文件的 BitMap 类。如果 bmp 不兼容,您应该会收到相关错误。

它与Wikipedia article 几乎完全一致。 (一个问题是它不适用于像素数组偏移量大于 255 的文件。这在代码中已注明,应该很容易修复。)

我一直将它与 mspaint 创建的 bmp 文件一起使用。

这是一个示例用法

example.cpp

#include "bmp.h"

int main() {
    // load the file. The constructor now does most of the work
    BitMap example_bmp("examplefile.bmp"); 

    // get the vector <R,G,B> for the pixel at (1,1)
    std::vector<unsigned int> example_vector = example_bmp.getPixel(1,1); 
}

example_vector 现在包含从图像顶部开始向下索引的坐标 (1,1) 处像素的 rgb(按此顺序)值。索引从 0 开始。请参阅 Wikipedia 示例。

这是头文件:

#ifndef BMP_H
#define BMP_H

#include <iostream>
#include <vector>
#include <fstream>
class BitMap {

    private:
        unsigned char m_bmpFileHeader[14];
        unsigned int m_pixelArrayOffset;
        unsigned char m_bmpInfoHeader[40];

        int m_height;
        int m_width;
        int m_bitsPerPixel;

        int m_rowSize;
        int m_pixelArraySize;

        unsigned char* m_pixelData;

        char * m_copyname;
        const char * m_filename;
    public:
        BitMap(const char * filename);
        ~BitMap();

        std::vector<unsigned int> getPixel(int i,int j);

        void makeCopy(char * filename);
        void writePixel(int i,int j, int R, int G, int B);

        void swapPixel(int i, int j, int i2, int j2);

        void dispPixelData();

        int width() {return m_width;}
        int height() {return m_height;}

        int vd(int i, int j);
        int hd(int i, int j);

        bool isSorted();
};

BitMap::BitMap( const char * filename) {

    using namespace std;

    m_filename = filename;

    ifstream inf(filename);
    if(!inf) {
        cerr<<"Unable to open file: "<<filename<<"\n";
    }



    //unsigned char m_bmpFileHeader[14];
    unsigned char a;
    for(int i =0;i<14;i++) {
        inf>>hex>>a;
        m_bmpFileHeader[i] = a;
    }
    if(m_bmpFileHeader[0]!='B' || m_bmpFileHeader[1]!='M') {
        cerr<<"Your info header might be different!\nIt should start with 'BM'.\n";
    }

    /*
        THE FOLLOWING LINE ONLY WORKS IF THE OFFSET IS 1 BYTE!!!!! (it can be 4 bytes max)
        That should be fixed now. 
        old line was
        m_pixelArrayOffset = m_bmpFileHeader[10];
    */
    unsigned int * array_offset_ptr = (unsigned int *)(m_bmpFileHeader + 10);
    m_pixelArrayOffset = *array_offset_ptr;


    if( m_bmpFileHeader[11] != 0 || m_bmpFileHeader[12] !=0 || m_bmpFileHeader[13] !=0 ) {
        std::cerr<< "You probably need to fix something. bmp.h("<<__LINE__<<")\n";
    }



    //unsigned char m_bmpInfoHeader[40];
    for(int i=0;i<40;i++) {
        inf>>hex>>a;
        m_bmpInfoHeader[i]=a;
    }

    int * width_ptr = (int*)(m_bmpInfoHeader+4);
    int * height_ptr = (int*)(m_bmpInfoHeader+8);

    m_width = *width_ptr;
    m_height = *height_ptr;

    printf("W: %i, H: %i", m_width, m_height);

    m_bitsPerPixel = m_bmpInfoHeader[14];
    if(m_bitsPerPixel!=24) {
        cerr<<"This program is for 24bpp files. Your bmp is not that\n";
    }
    int compressionMethod = m_bmpInfoHeader[16];
    if(compressionMethod!=0) {
        cerr<<"There's some compression stuff going on that we might not be able to deal with.\n";
        cerr<<"Comment out offending lines to continue anyways. bpm.h line: "<<__LINE__<<"\n";
    }


    m_rowSize = int( floor( (m_bitsPerPixel*m_width + 31.)/32 ) ) *4;
    m_pixelArraySize = m_rowSize* abs(m_height);

    m_pixelData = new unsigned char [m_pixelArraySize];

    inf.seekg(m_pixelArrayOffset,ios::beg);
    for(int i=0;i<m_pixelArraySize;i++) {
        inf>>hex>>a;
        m_pixelData[i]=a; 
    }



}

BitMap::~BitMap() {
    delete[] m_pixelData;
}

void BitMap::dispPixelData() {
    for(int i=0;i<m_pixelArraySize;i++) {
        std::cout<<(unsigned int)m_pixelData[i]<<" ";   
    }
    std::cout<<"\n";
}

// output is in rgb order.
std::vector<unsigned int> BitMap::getPixel(int x, int y) {
    if(x<m_width && y<m_height) {
        std::vector<unsigned int> v;
        v.push_back(0);
        v.push_back(0);
        v.push_back(0);

        y = m_height -1- y; //to flip things
        //std::cout<<"y: "<<y<<" x: "<<x<<"\n";
        v[0] = (unsigned int) ( m_pixelData[ m_rowSize*y+3*x+2 ] ); //red
        v[1] = (unsigned int) ( m_pixelData[ m_rowSize*y+3*x+1 ] ); //greed
        v[2] = (unsigned int) ( m_pixelData[ m_rowSize*y+3*x+0 ] ); //blue


        return v;
    }
    else {std::cerr<<"BAD INDEX\n";std::cerr<<"X: "<<x<<" Y: "<<y<<"\n";}
}

void BitMap::makeCopy(char * filename) {
    std::ofstream copyfile(filename);
    std::ifstream infile(m_filename);
    m_copyname = filename;

    unsigned char c;
    while(infile) {
        infile>>c;
        copyfile<<c;
    }
}

// changes the file
void BitMap::writePixel(int x,int y, int R, int G, int B) {
    std::fstream file(m_filename);
    y = m_height -1- y; // to flip things.
    int blueOffset = m_pixelArrayOffset+m_rowSize*y+3*x+0;

    // writes to the file
    file.seekg(blueOffset,std::ios::beg);
    file<< (unsigned char)B;
    file.seekg(blueOffset+1,std::ios::beg);
    file<< (unsigned char)G;
    file.seekg(blueOffset+2,std::ios::beg);
    file<< (unsigned char)R;

    // edits data in pixelData array 
    m_pixelData[m_rowSize*y+3*x+2] = (unsigned char)R;
    m_pixelData[m_rowSize*y+3*x+1] = (unsigned char)G;
    m_pixelData[m_rowSize*y+3*x+0] = (unsigned char)B;
}

// changes the file
void BitMap::swapPixel(int i, int j, int i2, int j2) {
    std::vector<unsigned int> p1 = (*this).getPixel(i,j);

    std::vector<unsigned int> p2 = (*this).getPixel(i2,j2);

    (*this).writePixel(i,j,p2[0],p2[1],p2[2]);
    (*this).writePixel(i2,j2,p1[0],p1[1],p1[2]);

}
#endif

【讨论】:

  • 我喜欢你的方法——但这种方法不起作用。它没有得到正确的高度和宽度。
  • @robben_ford_fan_boy 什么是正确的值,你得到了什么。我记得使用它有点广泛,虽然这个版本可能有错误
  • 我认为实际是 1300,而它的高度和宽度都是 20
  • @Robben_Ford_Fan_boy 好的,你是对的。我只将它用于小物体(精灵),所以这个问题从未出现过。我会解决的。
  • @Robben_Ford_Fan_boy 高度和宽度(以及像素阵列偏移)为它们保留了 4 个字节。我最初只使用一个字节。您可以在当前先前版本的THE FOLLOWING LINE ONLY WORKS IF THE OFFSET IS 1 BYTE!!!! 部分下查看相关更改。
【解决方案5】:

这是答案的工作 C++ 版本:

#include <fstream>
#include <iostream>
#include <string>
#include <array>
#include <vector>
#include <iterator>

std::vector<char> readBMP(const std::string &file)
{
    static constexpr size_t HEADER_SIZE = 54;

    std::ifstream bmp(file, std::ios::binary);

    std::array<char, HEADER_SIZE> header;
    bmp.read(header.data(), header.size());

    auto fileSize = *reinterpret_cast<uint32_t *>(&header[2]);
    auto dataOffset = *reinterpret_cast<uint32_t *>(&header[10]);
    auto width = *reinterpret_cast<uint32_t *>(&header[18]);
    auto height = *reinterpret_cast<uint32_t *>(&header[22]);
    auto depth = *reinterpret_cast<uint16_t *>(&header[28]);

    std::cout << "fileSize: " << fileSize << std::endl;
    std::cout << "dataOffset: " << dataOffset << std::endl;
    std::cout << "width: " << width << std::endl;
    std::cout << "height: " << height << std::endl;
    std::cout << "depth: " << depth << "-bit" << std::endl;

    std::vector<char> img(dataOffset - HEADER_SIZE);
    bmp.read(img.data(), img.size());

    auto dataSize = ((width * 3 + 3) & (~3)) * height;
    img.resize(dataSize);
    bmp.read(img.data(), img.size());

    char temp = 0;

    for (auto i = dataSize - 4; i >= 0; i -= 3)
    {
        temp = img[i];
        img[i] = img[i+2];
        img[i+2] = temp;

        std::cout << "R: " << int(img[i] & 0xff) << " G: " << int(img[i+1] & 0xff) << " B: " << int(img[i+2] & 0xff) << std::endl;
    }

    return img;
}

【讨论】:

  • 在第一个 bmp.read 中您还没有到达流的末尾吗?我认为第二个 bmp.read 什么都不做?
  • 非常重要:这会读取垂直翻转的图像。
  • 您能告诉我上面的代码需要做哪些更改才能读取 .png 文件吗?
【解决方案6】:

我无法评论顶级答案,因为我还没有足够的 stackoverflow 代表,但我只想指出该实现的一个非常关键的错误。

某些位图可以用负高度写入,因此当您尝试分配图像数据缓冲区时,您的代码会因std::bad_alloc 而崩溃。具有负高度的位图意味着图像数据从上到下存储,而不是传统的从下到上存储。因此,顶级答案的稍微好一点的版本是(仍然不包括具有不同字节顺序和字节大小的系统的可移植性):

unsigned char* readBMP(char* filename)
{
    int i;
    FILE* f = fopen(filename, "rb");
    unsigned char info[54];
    fread(info, sizeof(unsigned char), 54, f); // read the 54-byte header

    // extract image height and width from header
    int width, height;
    memcpy(&width, info + 18, sizeof(int));
    memcpy(&height, info + 22, sizeof(int));

    int heightSign = 1;
    if (height < 0){
        heightSign = -1;
    }

    int size = 3 * width * abs(height);
    unsigned char* data = new unsigned char[size]; // allocate 3 bytes per pixel
    fread(data, sizeof(unsigned char), size, f); // read the rest of the data at once
    fclose(f);

    if(heightSign == 1){
        for(i = 0; i < size; i += 3)
        {
            //code to flip the image data here....
        }
    }
    return data;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多