C++读取二进制PBM文件后图像失真错位,求排查与解决方法
问题
实现C++代码读取二进制PBM文件,已成功读取文件头(魔数、宽度、高度),但执行像素读取与矩阵转换代码后,可视化图像时出现失真、像素位置错位。
原像素读取代码:
std::vector<pixel> v; char c; while(input_file.get(c)) { for(int i=0; i<8; i++) { pixel p; p.r = p.g = p.b = (c & (1 << i)) != 0; v.push_back(p); } }
原矩阵转换代码:
int h = stoi(height), w = stoi(width); int counter = 0; for(int i=0; i<h; i++) { std::vector<pixel> row; for(int j=0; j<w; j++) row.push_back(v[counter++]); image.push_back(row); }
完整原测试代码:
struct Pixel { int r, g, b; }; typedef struct Pixel pixel; int main(int argc, char *argv[]) { if(argc < 3) return 1; std::string input = argv[1]; std::string output = argv[2]; std::ifstream input_file(input); std::ofstream output_file(output); std::vector<std::vector<pixel>> image; std::vector<pixel> v; std::string line_one, line_two, line_pixels; char magicNumber; std::string width, height; while(getline(input_file, line_one)) { if(line_one.size() > 0 && line_one.at(0) != '#') { magicNumber = line_one.at(1); break; } } while(getline(input_file, line_two)) { if(line_two.size() > 0 && line_two.at(0) != '#') { std::stringstream ss(line_two); getline(ss, width, ' '); getline(ss, height, ' '); break; } } std::cout << magicNumber << std::endl; std::cout << width << " " << height << std::endl; if(magicNumber == '4') { std::vector<pixel> v; char c; while(input_file.get(c)) { for(int i=0; i<8; i++) { pixel p; p.r = p.g = p.b = (c & (1 << i)) != 0; v.push_back(p); } } int h = stoi(height), w = stoi(width); int counter = 0; for(int i=0; i<h; i++) { std::vector<pixel> row; for(int j=0; j<w; j++) row.push_back(v[counter++]); image.push_back(row); } } output_file << "P1" << std::endl << width << " " << height << std::endl; for(int i=0; i<stoi(height); i++) { for(int j=0; j<stoi(width); j++) { output_file << image[i][j].r << " "; } output_file << std::endl; } return 0; }
问题分析
导致图像失真错位的核心问题有三个:
- 比特位读取顺序错误:P4格式中,每个字节的比特位是从**最高位(第7位)到最低位(第0位)**依次对应图像的像素,原代码从第0位开始读取,导致每个字节内的像素顺序完全颠倒。
- 未处理行填充比特:P4要求每行的字节数向上取整为8的倍数,例如宽度为10的行,实际会用2个字节(16比特)存储,末尾6个比特是无效填充位,原代码把这些填充位也当作有效像素读取,导致后续行的像素位置整体偏移。
- 二进制文件打开模式错误:原代码用默认文本模式打开文件,在Windows系统下会自动转换换行符(将
\r\n转为\n),破坏二进制数据的完整性,必须用std::ios::binary模式打开。
修正后的完整代码
#include <iostream> #include <fstream> #include <vector> #include <sstream> #include <string> struct Pixel { int r, g, b; }; typedef struct Pixel pixel; int main(int argc, char *argv[]) { if(argc < 3) return 1; std::string input = argv[1]; std::string output = argv[2]; // 用二进制模式打开输入文件,避免文本模式解析破坏数据 std::ifstream input_file(input, std::ios::binary); std::ofstream output_file(output); std::vector<std::vector<pixel>> image; std::vector<pixel> v; std::string line_one, line_two; char magicNumber; std::string width_str, height_str; int width, height; // 读取魔数,跳过注释行 while(getline(input_file, line_one)) { if(line_one.size() > 0 && line_one[0] != '#') { magicNumber = line_one[1]; break; } } // 读取宽高,跳过注释行 while(getline(input_file, line_two)) { if(line_two.size() > 0 && line_two[0] != '#') { std::stringstream ss(line_two); ss >> width_str >> height_str; width = std::stoi(width_str); height = std::stoi(height_str); break; } } std::cout << magicNumber << std::endl; std::cout << width << " " << height << std::endl; if(magicNumber == '4') { // 计算每行实际需要的字节数(向上取整到8的倍数) int bytes_per_row = (width + 7) / 8; char c; for(int row = 0; row < height; ++row) { std::vector<pixel> current_row; for(int byte_idx = 0; byte_idx < bytes_per_row; ++byte_idx) { input_file.get(c); // 从最高位(第7位)到最低位(第0位)读取比特 for(int bit_idx = 7; bit_idx >= 0; --bit_idx) { if(current_row.size() >= width) { break; // 跳过当前行的填充比特 } pixel p; p.r = p.g = p.b = (c & (1 << bit_idx)) != 0; current_row.push_back(p); } } image.push_back(current_row); } } // 输出为P1格式的文本PBM output_file << "P1" << std::endl << width << " " << height << std::endl; for(const auto& row : image) { for(size_t j = 0; j < row.size(); ++j) { output_file << row[j].r; if(j != row.size() - 1) { output_file << " "; } } output_file << std::endl; } return 0; }
内容的提问来源于stack exchange,提问作者Kleber Mota
相关产品推荐
相关产品推荐

