diff --git a/CMakeLists.txt b/CMakeLists.txt index 1dccc3ab58..51e0b19f94 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -282,6 +282,7 @@ OCV_OPTION(WITH_PROTOBUF "Enable libprotobuf" ON OCV_OPTION(WITH_IMGCODEC_HDR "Include HDR support" ON) OCV_OPTION(WITH_IMGCODEC_SUNRASTER "Include SUNRASTER support" ON) OCV_OPTION(WITH_IMGCODEC_PXM "Include PNM (PBM,PGM,PPM) and PAM formats support" ON) +OCV_OPTION(WITH_IMGCODEC_PFM "Include PFM formats support" ON) # OpenCV build components # =================================================== @@ -1227,6 +1228,10 @@ if(WITH_IMGCODEC_PXM OR DEFINED HAVE_IMGCODEC_PXM) status(" PXM:" HAVE_IMGCODEC_PXM THEN "YES" ELSE "NO") endif() +if(WITH_IMGCODEC_PFM OR DEFINED HAVE_IMGCODEC_PFM) + status(" PFM:" HAVE_IMGCODEC_PFM THEN "YES" ELSE "NO") +endif() + # ========================== VIDEO IO ========================== status("") status(" Video I/O:") diff --git a/cmake/OpenCVFindLibsGrfmt.cmake b/cmake/OpenCVFindLibsGrfmt.cmake index 9f18e2bf62..91afcc4f12 100644 --- a/cmake/OpenCVFindLibsGrfmt.cmake +++ b/cmake/OpenCVFindLibsGrfmt.cmake @@ -268,3 +268,8 @@ if(WITH_IMGCODEC_PXM) elseif(DEFINED WITH_IMGCODEC_PXM) set(HAVE_IMGCODEC_PXM OFF) endif() +if(WITH_IMGCODEC_PFM) + set(HAVE_IMGCODEC_PFM ON) +elseif(DEFINED WITH_IMGCODEC_PFM) + set(HAVE_IMGCODEC_PFM OFF) +endif() \ No newline at end of file diff --git a/modules/imgcodecs/CMakeLists.txt b/modules/imgcodecs/CMakeLists.txt index 434278c2a5..d771224d4c 100644 --- a/modules/imgcodecs/CMakeLists.txt +++ b/modules/imgcodecs/CMakeLists.txt @@ -72,6 +72,10 @@ if(HAVE_IMGCODEC_PXM) add_definitions(-DHAVE_IMGCODEC_PXM) endif() +if (HAVE_IMGCODEC_PFM) + add_definitions(-DHAVE_IMGCODEC_PFM) +endif() + file(GLOB grfmt_hdrs ${CMAKE_CURRENT_LIST_DIR}/src/grfmt*.hpp) file(GLOB grfmt_srcs ${CMAKE_CURRENT_LIST_DIR}/src/grfmt*.cpp) diff --git a/modules/imgcodecs/src/grfmt_pfm.cpp b/modules/imgcodecs/src/grfmt_pfm.cpp new file mode 100644 index 0000000000..d044042719 --- /dev/null +++ b/modules/imgcodecs/src/grfmt_pfm.cpp @@ -0,0 +1,262 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#include "precomp.hpp" +#include "utils.hpp" +#include "grfmt_pfm.hpp" +#include + +#ifdef HAVE_IMGCODEC_PFM + +namespace { + +static_assert(sizeof(float) == 4, "float must be 32 bit."); + + +bool is_byte_order_swapped(double scale) +{ + // ".pfm" format file specifies that: + // positive scale means big endianess; + // negative scale means little endianess. + + #ifdef WORDS_BIGENDIAN + return scale < 0.0; + #else + return scale >= 0.0; + #endif +} + +void swap_endianess(uint32_t& ui) +{ + static const uint32_t A(0x000000ffU); + static const uint32_t B(0x0000ff00U); + static const uint32_t C(0x00ff0000U); + static const uint32_t D(0xff000000U); + + ui = ( (ui & A) << 24 ) + | ( (ui & B) << 8 ) + | ( (ui & C) >> 8 ) + | ( (ui & D) >> 24 ); +} + +template T atoT(const std::string& s); +template<> int atoT(const std::string& s) { return std::atoi(s.c_str()); } +template<> double atoT(const std::string& s) { return std::atof(s.c_str()); } + +template +T read_number(cv::RLByteStream& strm) +{ + // should be enogh to take string representation of any number + const size_t buffer_size = 2048; + + std::vector buffer(buffer_size, 0); + for (size_t i = 0; i < buffer_size; ++i) { + const int intc = strm.getByte(); + CV_Assert(intc >= -128 && intc < 128); + char c = static_cast(intc); + if (std::isspace(c)) { + break; + } + buffer[i] = c; + } + const std::string str(buffer.begin(), buffer.end()); + return atoT(str); +} + +template void write_anything(cv::WLByteStream& strm, const T& t) +{ + std::ostringstream ss; + ss << t; + strm.putBytes(ss.str().c_str(), static_cast(ss.str().size())); +} + +} + +namespace cv { + +PFMDecoder::~PFMDecoder() +{ +} + +PFMDecoder::PFMDecoder() +{ + m_strm.close(); +} + +bool PFMDecoder::readHeader() +{ + if (m_buf.empty()) { + if (!m_strm.open(m_filename)) { + return false; + } + } else { + if (!m_strm.open(m_buf)) { + return false; + } + } + + if (m_strm.getByte() != 'P') { + CV_Error(Error::StsError, "Unexpected file type (expected P)"); + } + + switch (m_strm.getByte()) { + case 'f': + m_type = CV_32FC1; + break; + case 'F': + m_type = CV_32FC3; + break; + default: + CV_Error(Error::StsError, "Unexpected file type (expected `f` or `F`)"); + } + + if ('\n' != m_strm.getByte()) { + CV_Error(Error::StsError, "Unexpected header format (expected line break)"); + } + + + m_width = read_number(m_strm); + m_height = read_number(m_strm); + m_scale_factor = read_number(m_strm); + m_swap_byte_order = is_byte_order_swapped(m_scale_factor); + + return true; +} + +bool PFMDecoder::readData(Mat& mat) +{ + if (!m_strm.isOpened()) { + CV_Error(Error::StsError, "Unexpected status in data stream"); + } + + Mat buffer(mat.size(), m_type); + for (int y = m_height - 1; y >= 0; --y) { + m_strm.getBytes(buffer.ptr(y), static_cast(m_width * buffer.elemSize())); + if (is_byte_order_swapped(m_scale_factor)) { + for (int i = 0; i < m_width * buffer.channels(); ++i) { + static_assert( sizeof(uint32_t) == sizeof(float), + "uint32_t and float must have same size." ); + swap_endianess(buffer.ptr(y)[i]); + } + } + } + + if (buffer.channels() == 3) { + cv::cvtColor(buffer, buffer, cv::COLOR_BGR2RGB); + } + + CV_Assert(fabs(m_scale_factor) > 0.0f); + buffer *= 1.f / fabs(m_scale_factor); + + buffer.convertTo(mat, mat.type()); + + return true; +} + +size_t PFMDecoder::signatureLength() const +{ + return 3; +} + +bool PFMDecoder::checkSignature( const String& signature ) const +{ + return signature.size() >= 3 + && signature[0] == 'P' + && ( signature[1] == 'f' || signature[1] == 'F' ) + && isspace(signature[2]); +} + +void PFMDecoder::close() +{ + // noop +} + +////////////////////////////////////////////////////////////////////////////////////////// + +PFMEncoder::PFMEncoder() +{ + m_description = "Portable image format - float (*.pfm)"; +} + +PFMEncoder::~PFMEncoder() +{ +} + +bool PFMEncoder::isFormatSupported(int depth) const +{ + return CV_MAT_DEPTH(depth) == CV_32F || CV_MAT_DEPTH(depth) == CV_8U; +} + +bool PFMEncoder::write(const Mat& img, const std::vector& params) +{ + (void) params; + + WLByteStream strm; + if (m_buf) { + if (!strm.open(*m_buf)) { + return false; + } else { + m_buf->reserve(alignSize(256 + sizeof(float) * img.channels() * img.total(), 256)); + } + } else if (!strm.open(m_filename)) { + return false; + } + + Mat float_img; + strm.putByte('P'); + switch (img.channels()) { + case 1: + strm.putByte('f'); + img.convertTo(float_img, CV_32FC1); + break; + case 3: + strm.putByte('F'); + img.convertTo(float_img, CV_32FC3); + break; + default: + CV_Error(Error::StsBadArg, "Expected 1 or 3 channel image."); + } + strm.putByte('\n'); + + + write_anything(strm, float_img.cols); + strm.putByte(' '); + write_anything(strm, float_img.rows); + strm.putByte('\n'); +#ifdef WORDS_BIGENDIAN + write_anything(strm, 1.0); +#else + write_anything(strm, -1.0); +#endif + + strm.putByte('\n'); + + // Comments are not officially supported in this file format. + // write_anything(strm, "# Generated by OpenCV " CV_VERSION "\n"); + + for (int y = float_img.rows - 1; y >= 0; --y) + { + if (float_img.channels() == 3) { + const float* bgr_row = float_img.ptr(y); + size_t row_size = float_img.cols * float_img.channels(); + std::vector rgb_row(row_size); + for (int x = 0; x < float_img.cols; ++x) { + rgb_row[x*3+0] = bgr_row[x*3+2]; + rgb_row[x*3+1] = bgr_row[x*3+1]; + rgb_row[x*3+2] = bgr_row[x*3+0]; + } + strm.putBytes( reinterpret_cast(rgb_row.data()), + static_cast(sizeof(float) * row_size)); + } else if (float_img.channels() == 1) { + strm.putBytes(float_img.ptr(y), sizeof(float) * float_img.cols); + } + } + return true; +} + + +} + + +#endif // HAVE_IMGCODEC_PFM diff --git a/modules/imgcodecs/src/grfmt_pfm.hpp b/modules/imgcodecs/src/grfmt_pfm.hpp new file mode 100644 index 0000000000..9284f60515 --- /dev/null +++ b/modules/imgcodecs/src/grfmt_pfm.hpp @@ -0,0 +1,57 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#ifndef _GRFMT_PFM_H_ +#define _GRFMT_PFM_H_ + +#include "grfmt_base.hpp" +#include "bitstrm.hpp" + +#ifdef HAVE_IMGCODEC_PFM +namespace cv +{ + +class PFMDecoder CV_FINAL : public BaseImageDecoder +{ +public: + PFMDecoder(); + virtual ~PFMDecoder() CV_OVERRIDE; + + bool readData( Mat& img ) CV_OVERRIDE; + bool readHeader() CV_OVERRIDE; + void close(); + + size_t signatureLength() const CV_OVERRIDE; + bool checkSignature( const String& signature ) const CV_OVERRIDE; + ImageDecoder newDecoder() const CV_OVERRIDE + { + return makePtr(); + } + +private: + RLByteStream m_strm; + double m_scale_factor; + bool m_swap_byte_order; +}; + +class PFMEncoder CV_FINAL : public BaseImageEncoder +{ +public: + PFMEncoder(); + virtual ~PFMEncoder() CV_OVERRIDE; + + bool isFormatSupported( int depth ) const CV_OVERRIDE; + bool write( const Mat& img, const std::vector& params ) CV_OVERRIDE; + + ImageEncoder newEncoder() const CV_OVERRIDE + { + return makePtr(); + } +}; + +} + +#endif // HAVE_IMGCODEC_PXM + +#endif/*_GRFMT_PFM_H_*/ \ No newline at end of file diff --git a/modules/imgcodecs/src/grfmts.hpp b/modules/imgcodecs/src/grfmts.hpp index 10bd88264e..4fd58d022d 100644 --- a/modules/imgcodecs/src/grfmts.hpp +++ b/modules/imgcodecs/src/grfmts.hpp @@ -47,6 +47,7 @@ #include "grfmt_sunras.hpp" #include "grfmt_jpeg.hpp" #include "grfmt_pxm.hpp" +#include "grfmt_pfm.hpp" #include "grfmt_tiff.hpp" #include "grfmt_png.hpp" #include "grfmt_jpeg2000.hpp" diff --git a/modules/imgcodecs/src/loadsave.cpp b/modules/imgcodecs/src/loadsave.cpp index f5d9c98a3b..2044409435 100644 --- a/modules/imgcodecs/src/loadsave.cpp +++ b/modules/imgcodecs/src/loadsave.cpp @@ -156,6 +156,10 @@ struct ImageCodecInitializer decoders.push_back( makePtr() ); encoders.push_back( makePtr() ); #endif + #ifdef HAVE_IMGCODEC_PFM + decoders.push_back( makePtr() ); + encoders.push_back( makePtr() ); + #endif #ifdef HAVE_TIFF decoders.push_back( makePtr() ); encoders.push_back( makePtr() ); diff --git a/modules/imgcodecs/test/test_grfmt.cpp b/modules/imgcodecs/test/test_grfmt.cpp index 70b2f2714e..1b1eaa743e 100644 --- a/modules/imgcodecs/test/test_grfmt.cpp +++ b/modules/imgcodecs/test/test_grfmt.cpp @@ -158,6 +158,7 @@ TEST_P(Imgcodecs_ExtSize, write_imageseq) Mat img_gt(size, CV_MAKETYPE(CV_8U, cn), Scalar::all(0)); circle(img_gt, center, radius, Scalar::all(255)); + #if 1 if (ext == ".pbm" || ext == ".pgm" || ext == ".ppm") { @@ -172,6 +173,7 @@ TEST_P(Imgcodecs_ExtSize, write_imageseq) EXPECT_EQ(img.type(), img.type()); EXPECT_EQ(cn, img.channels()); + if (ext == ".jpg") { // JPEG format does not provide 100% accuracy @@ -181,14 +183,21 @@ TEST_P(Imgcodecs_ExtSize, write_imageseq) EXPECT_LT(n, expected); EXPECT_PRED_FORMAT2(cvtest::MatComparator(10, 0), img, img_gt); } + else if (ext == ".pfm") + { + img_gt.convertTo(img_gt, CV_MAKETYPE(CV_32F, img.channels())); + double n = cvtest::norm(img, img_gt, NORM_L2); + EXPECT_LT(n, 1.); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), img, img_gt); + } else { double n = cvtest::norm(img, img_gt, NORM_L2); EXPECT_LT(n, 1.); EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), img, img_gt); } + #if 0 - std::cout << filename << std::endl; imshow("loaded", img); waitKey(0); #else @@ -214,7 +223,10 @@ const string all_exts[] = ".ppm", ".pgm", ".pbm", - ".pnm" + ".pnm", +#endif +#ifdef HAVE_IMGCODEC_PFM + ".pfm", #endif }; @@ -337,6 +349,30 @@ TEST(Imgcodecs_Pam, read_write) } #endif +#ifdef HAVE_IMGCODEC_PFM +TEST(Imgcodecs_Pfm, read_write) +{ + Mat img = imread(findDataFile("readwrite/lena.pam")); + ASSERT_FALSE(img.empty()); + img.convertTo(img, CV_32F, 1/255.0f); + + std::vector params; + string writefile = cv::tempfile(".pfm"); + EXPECT_NO_THROW(cv::imwrite(writefile, img, params)); + cv::Mat reread = cv::imread(writefile, IMREAD_UNCHANGED); + + string writefile_no_param = cv::tempfile(".pfm"); + EXPECT_NO_THROW(cv::imwrite(writefile_no_param, img)); + cv::Mat reread_no_param = cv::imread(writefile_no_param, IMREAD_UNCHANGED); + + EXPECT_EQ(0, cvtest::norm(reread, reread_no_param, NORM_INF)); + EXPECT_EQ(0, cvtest::norm(img, reread, NORM_INF)); + + EXPECT_EQ(0, remove(writefile.c_str())); + EXPECT_EQ(0, remove(writefile_no_param.c_str())); +} +#endif + TEST(Imgcodecs, write_parameter_type) { cv::Mat m(10, 10, CV_8UC1, cv::Scalar::all(0));