使用OpenCV保存StereoSGBM视差图为PFM格式时图像水平重复四次的问题
问题:视差图保存为PFM格式后水平方向重复四次
使用cv::StereoSGBM进行立体匹配得到视差图,调用GitHub上的PFM读写代码保存为PFM格式后,输出图像在水平方向重复了四次。对比保存为JPG格式的正常参考图,求该问题的原因及解决方法。
相关代码
PFMReadWrite.cpp
bool savePFM(const cv::Mat image, const std::string filePath) { //Open the file as binary! ofstream imageFile(filePath.c_str(), ios::out | ios::trunc | ios::binary); if(imageFile) { int width(image.cols), height(image.rows); int numberOfComponents(image.channels()); //Write the type of the PFM file and ends by a line return char type[3]; type[0] = 'P'; type[2] = 0x0a; if(numberOfComponents == 3) { type[1] = 'F'; } else if(numberOfComponents == 1) { type[1] = 'f'; } imageFile << type[0] << type[1] << type[2]; //Write the width and height and ends by a line return imageFile << width << " " << height << type[2]; //Assumes little endian storage and ends with a line return 0x0a //Stores the type char byteOrder[10]; byteOrder[0] = '-'; byteOrder[1] = '1'; byteOrder[2] = '.'; byteOrder[3] = '0'; byteOrder[4] = '0'; byteOrder[5] = '0'; byteOrder[6] = '0'; byteOrder[7] = '0'; byteOrder[8] = '0'; byteOrder[9] = 0x0a; for(int i = 0 ; i<10 ; ++i) { imageFile << byteOrder[i]; } //Store the floating points RGB color upside down, left to right float* buffer = new float[numberOfComponents]; for(int i = 0 ; i<height ; ++i) { for(int j = 0 ; j<width ; ++j) { if(numberOfComponents == 1) { buffer[0] = image.at<float>(height-1-i,j); } else { Vec3f color = image.at<Vec3f>(height-1-i,j); //OpenCV stores as BGR buffer[0] = color.val[2]; buffer[1] = color.val[1]; buffer[2] = color.val[0]; } //Write the values imageFile.write((char *) buffer, numberOfComponents*sizeof(float)); } } delete[] buffer; imageFile.close(); } else { cerr << "Could not open the file : " << filePath << endl; return false; } return true; }
main.cpp
// Creating Disp map stereomatching.computeDisparity(left, right, imgsize, disp); // Writing PFM cv::imwrite(outputpath + ".jpg", disp); bool r = savePFM(disp, outputpath + ".pfm");
原因分析
核心问题是视差图的Mat数据类型与savePFM函数的预期不匹配:
cv::StereoSGBM输出的视差图默认是16位有符号/无符号整数类型(CV_16S或CV_16U),而savePFM函数直接用image.at<float>()访问像素,假设输入是32位浮点类型(CV_32F)。- 当用
at<float>()读取非浮点类型的Mat时,会将连续4个字节(float占4字节)解析为一个浮点值。而16位整数仅占2字节,这就导致每读取一个"错误的float值",实际跳过了2个字节的像素数据,最终图像在水平方向被错误拉伸/重复4倍(字节数不匹配导致的读取范围异常)。
解决方法
方法1:提前转换视差图为浮点类型
在调用savePFM前,将16位整数视差图转换为32位浮点类型,同时注意SGBM的视差值通常需要除以16(因为SGBM的disparity参数一般设为16的倍数,输出视差是实际值×16):
cv::Mat disp_float; disp.convertTo(disp_float, CV_32F, 1.0/16.0); // 转换为浮点型并还原真实视差 bool r = savePFM(disp_float, outputpath + ".pfm");
方法2:增强savePFM函数的类型兼容性
修改函数,自动处理不同输入类型的转换,避免依赖外部转换:
bool savePFM(const cv::Mat image, const std::string filePath) { cv::Mat float_image; // 根据输入类型转换为浮点型 if (image.type() != CV_32F && image.type() != CV_32FC3) { if (image.channels() == 1) { // 单通道整数转浮点,保持数值比例 image.convertTo(float_image, CV_32F, 1.0); } else { // 多通道整数转浮点,同时转换BGR到RGB cv::cvtColor(image, float_image, cv::COLOR_BGR2RGB); float_image.convertTo(float_image, CV_32F, 1.0/255.0); } } else { float_image = image.clone(); if (image.channels() == 3) { cv::cvtColor(float_image, float_image, cv::COLOR_BGR2RGB); } } // 后续使用转换后的float_image处理 ofstream imageFile(filePath.c_str(), ios::out | ios::trunc | ios::binary); if(!imageFile) { cerr << "Could not open the file : " << filePath << endl; return false; } int width(float_image.cols), height(float_image.rows); int numberOfComponents(float_image.channels()); char type[3]; type[0] = 'P'; type[2] = 0x0a; type[1] = (numberOfComponents == 3) ? 'F' : 'f'; imageFile << type[0] << type[1] << type[2]; imageFile << width << " " << height << type[2]; // 写入字节序信息 char byteOrder[] = "-1.00000\n"; imageFile << byteOrder; float* buffer = new float[numberOfComponents]; for(int i = 0 ; i<height ; ++i) { for(int j = 0 ; j<width ; ++j) { if(numberOfComponents == 1) { buffer[0] = float_image.at<float>(height-1-i,j); } else { Vec3f color = float_image.at<Vec3f>(height-1-i,j); buffer[0] = color.val[0]; buffer[1] = color.val[1]; buffer[2] = color.val[2]; } imageFile.write((char *) buffer, numberOfComponents*sizeof(float)); } } delete[] buffer; imageFile.close(); return true; }
方法3:验证输入Mat类型
在调用savePFM前打印视差图的类型,确认是否符合预期:
std::cout << "视差图Mat类型: " << cv::typeToString(disp.type()) << std::endl;
内容的提问来源于stack exchange,提问作者voltaic
相关产品推荐
相关产品推荐

