【问题标题】:Save exr/pfm as little endian将 exr/pfm 保存为小端
【发布时间】:2019-12-05 10:07:48
【问题描述】:

我将 bmp 文件加载到 CImg 对象中,并将其保存到 pfm 文件中。成功的。而这个 .pfm 文件我正在将它用于另一个库,但是这个库不接受 big-endian,只接受 little endian

    CImg<float> image;
    image.load_bmp(_T("D:\\Temp\\memorial.bmp"));
    image.normalize(0.0, 1.0);
    image.save_pfm(_T("D:\\Temp\\memorial.pfm"));

那么,如何将 bmp 文件保存到 pfm 文件little endiannot big endian 。 . 有可能吗?

后期编辑:

我检查了 .pfm 头文件中的前 5 个元素。这是没有 invert_endianness 的结果:

CImg<float> image;
image.load_bmp(_T("D:\\Temp\\memorial.bmp"));
image.normalize(0.0, 1.0);
image.save_pfm(_T("D:\\Temp\\memorial.pfm"));

PF
512
768
1.0
=øøù=€€=‘>

这是invert_endianness的结果:

CImg<float> image;
image.load_bmp(_T("D:\\Temp\\memorial.bmp"));
image.invert_endianness();
image.normalize(0.0, 1.0);
image.save_pfm(_T("D:\\Temp\\memorial.pfm"));

PF
512
768
1.0
?yôx?!ù=‚ì:„ç‹?

结果是一样的。

【问题讨论】:

  • 将使用和不使用invert_endianness() 获得的 PFM 标题行添加到您的问题中。 Scale Factor / Endianness 行是最重要的。
  • 太好了,这就是我需要的输出。事实上,它看起来不像是反转字节序。如果您这样做会发生什么:1. 读取 BMP,2. 写入 PFM。 3. 阅读 PFM。 4. 反转字节序。 5. 再次写入 PFM。我在想CImg 可能无法在 BMP 上反转字节顺序 - 但这是一个远景。
  • ...但有一件事:您显示的 PFM 文件中的 实际 图像字节在使用和不使用 invert_endianness 时是不同的。如果您手动进入并将比例因子/字节序从1.0 更改为-1.0,会发生什么?或者如果你image.invert_endianness(); 之后 image.normalize(0.0, 1.0);?无论如何,那些标准化值应该做什么?我觉得它们有点奇怪。
  • 好的,但是您使用的实际值与我在文档中看到的示例中使用的值不同。我自己刚刚下载了CImg 并会尝试一下。
  • 您好 Ted,我几天不在办公室。我会尝试你的解决方案,我会让你知道结果。

标签: c++ image image-processing visual-c++ cimg


【解决方案1】:

这绝对不是一个正确的答案,但暂时可以作为一种解决方法。

我没有找到如何使用CImgs 函数正确反转字节序,所以我修改了结果文件。这是一个黑客。结果在 GIMP 中打开良好,看起来非常接近原始图像,但我不能说它是否适用于您正在使用的库。可能值得一试。

代码中的注释:

#include "CImg/CImg.h"

#include <algorithm>
#include <filesystem> // >= C++17 must be selected as Language Standard
#include <ios>
#include <iostream>
#include <iterator>
#include <fstream>
#include <string>

using namespace cimg_library;
namespace fs = std::filesystem;

// a class to remove temporary files
class remove_after_use {
public:
    remove_after_use(const std::string& filename) : name(filename) {}
    remove_after_use(const remove_after_use&) = delete;
    remove_after_use& operator=(const remove_after_use&) = delete;

    const char* c_str() const { return name.c_str(); }
    operator std::string const& () const { return name; }

    ~remove_after_use() {
        try {
            fs::remove(name);
        }
        catch (const std::exception & ex) {
            std::cerr << "remove_after_use: " << ex.what() << "\n";
        }
    }
private:
    std::string name;
};

// The function to hack the file saved by CImg
template<typename T>
bool save_pfm_endianness_inverted(const T& img, const std::string& filename) {
    remove_after_use tempfile("tmp.pfm");

    // get CImg's endianness inverted image and save it to a temporary file
    img.get_invert_endianness().save_pfm(tempfile.c_str());

    // open the final file
    std::ofstream os(filename, std::ios::binary);

    // read "tmp.pfm" and modify
    // The Scale Factor / Endianness line
    if (std::ifstream is; os && (is = std::ifstream(tempfile, std::ios::binary))) {
        std::string lines[3];
        // Read the 3 PFM header lines as they happen to be formatted by
        // CImg. Will maybe not work with another library.
        size_t co = 0;
        for (; co < std::size(lines) && std::getline(is, lines[co]); ++co);

        if (co == std::size(lines)) { // success
            // write the first two lines back unharmed:
            os << lines[0] << '\n' << lines[1] << '\n';

            if (lines[2].empty()) {
                std::cerr << "something is wrong with the pfm header\n";
                return false;
            }

            // add a '-' if it's missing, remove it if it's there: 
            if (lines[2][0] == '-') {       // remove the - to invert
                os << lines[2].substr(1);
            }
            else {                          // add a - to invert
                os << '-' << lines[2] << '\n';
            }

            // copy all the rest as-is:
            std::copy(std::istreambuf_iterator<char>(is),
                std::istreambuf_iterator<char>{},
                std::ostreambuf_iterator<char>(os));
        }
        else {
            std::cerr << "failed reading pfm header\n";
            return false;
        }
    }
    else {
        std::cerr << "opening files failed\n";
        return false;
    }
    return true;
}

int main()
{
    CImg<float> img("memorial.bmp");
    img.normalize(0.f, 1.f);
    std::cout << "saved ok: " << std::boolalpha
              << save_pfm_endianness_inverted(img, "memorial.pfm") << "\n";
}

【讨论】:

  • 现在似乎没问题,did 是一种解决方法。泰德,你能给我解释一下这段代码吗? // 按原样复制所有其余部分:std::copy(std::istreambuf_iterator(is), std::istreambuf_iterator{}, std::ostreambuf_iterator(os));跨度>
  • @flaviu2 很高兴该解决方法似乎有效。也许错误报告将是有序的。 std::istreambuf_iterator&lt;char&gt;(is) 使用 istream 对象 (is) 创建一个输入迭代器。 std::istreambuf_iterator&lt;char&gt;{} 创建一个 end-of-stream 迭代器std::ostreambuf_iterator&lt;char&gt;(os) 使用 os 创建一个输出迭代器 - 然后 std::copy 用于从输入迭代器复制到输出迭代器 - 所以它将文件的其余部分从当前is 位置复制到当前os 位置。
  • Ted,想复制你的想法,我遇到了一个问题,你能看看吗?我希望我不会碰运气:)
【解决方案2】:

想要以经典的 C++ 风格(作为语言的缘故)解决相同的问题,我写道:

BOOL CMyDoc::SavePfmEndiannessInverted(CImg<float>& img, const CString sFileName)
{
CString sDrive, sDir;
_splitpath(sFileName, sDrive.GetBuffer(), sDir.GetBuffer(), NULL, NULL);

CString sTemp;
sTemp.Format(_T("%s%sTemp.tmp"), sDrive, sDir);

sDrive.ReleaseBuffer();
sDir.ReleaseBuffer();

CRemoveAfterUse TempFile(sTemp);
img.get_invert_endianness().save_pfm(TempFile.c_str());

CFile fileTemp;
if (! fileTemp.Open(sTemp, CFile::typeBinary))
    return FALSE;

char c;
UINT nRead = 0;
int nCount = 0;
ULONGLONG nPosition = 0;
CString sScale;
CByteArray arrHeader, arrData;
do
{
    nRead = fileTemp.Read((char*)&c, sizeof(char));
    switch (nCount)
    {
    case 0:
    case 1:
        arrHeader.Add(static_cast<BYTE>(c));
        break;
    case 2:             // retrieve the '1.0' string
        sScale += c;
        break;
    }
    if ('\n' == c)      // is new line
    {
        nCount++;
    }
    if (nCount >= 3)    // read the header, so go out
    {
        nPosition = fileTemp.GetPosition();
        break;
    }
}while (nRead > 0);

if (nPosition > 1)
{
    arrData.SetSize(fileTemp.GetLength() - nPosition);
    fileTemp.Read(arrData.GetData(), (UINT)arrData.GetSize());
}

fileTemp.Close();

CFile file;
if (! file.Open(sFileName, CFile::typeBinary | CFile::modeCreate | CFile::modeReadWrite))
    return FALSE;

CByteArray arrTemp;
ConvertCStringToByteArray(sScale, arrTemp);
arrHeader.Append(arrTemp);
arrHeader.Append(arrData);
file.Write(arrHeader.GetData(), (UINT)arrHeader.GetSize());
file.Close();

return TRUE;
}

但似乎没有完成这项工作,因为图像结果更暗

我在这里做错了什么?代码在我看来很清楚,但仍然没有按预期工作......

当然,我知道这种方法效率较低,但正如我之前所说,只是为了语言。

我认为我的代码没有问题 :)

这里是试用:

CImg<float> image;
image.load_bmp(_T("D:\\Temp\\memorial.bmp"));
image.normalize(0.0f, 1.0f);
image.save_pfm(_T("D:\\Temp\\memorial.pfm"));
image.get_invert_endianness().save(_T("D:\\Temp\\memorial_inverted.pfm"));

纪念馆.pfm 看起来像这样:

memorial_inverted.pfm 看起来像这样:

【讨论】:

  • 我使用的都是标准的 C++ 函数/类。这个带有“classic C++ style”的新问题使用了我不熟悉的 MFC(Microsoft Foundation Classes)——主要是因为我尝试尽可能多地使用标准 C++ 库可移植性——对于这项任务,我认为没有理由同时放弃标准库和松散的可移植性。在 Microsoft 世界之外,您不会发现 MFC 有太多用途,因此我的建议是让您自己熟悉标准库。它最终会得到回报:-)
  • 我不认为我可以避免 MFC,它是一个 MFC 项目 :)
  • :-) 你不必避免它,但你也不需要将它用于使用标准更容易解决的事情。关于图片。它有点像我使用CImg::invert_endianness 时得到的图片。文件中的实际数据是字节序反转的,但Scale Factor / Endianness 行仍然显示1.0。您是否查看了 pfm 文件以确认您已成功反转 Scale Factor / Endianness 行上的值?
  • 是的,比例因子已正确写入。结果图像稍微白了一点,但同样的转换使它与在线转换器输出相同的图像。
  • 好的,你现在得到的结果和我的代码一样吗?如果它更亮,我希望这是正常化的结果。如果您:1 读取 BMP,2 标准化,3 将其保存为新的 BMP,会发生什么情况。 - 这个新的 BMP 是否也有变亮的迹象?
猜你喜欢
  • 1970-01-01
  • 2018-01-10
  • 1970-01-01
  • 1970-01-01
  • 2011-04-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-06
相关资源
最近更新 更多