mirror of
https://github.com/zebrajr/opencv.git
synced 2026-01-15 12:15:17 +00:00
imgcodecs: OpenEXR multispectral read:write support (#27485)
imgcodecs: OpenEXR multispectral read/write support #27485 OpenCV Extra: https://github.com/opencv/opencv_extra/pull/1262/ Adds capability to read and write multispectral (>4 channels) images in OpenEXR format. ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [ ] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [ ] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
@@ -484,7 +484,7 @@ filename extension (see cv::imread for the list of extensions). In general, only
|
||||
single-channel or 3-channel (with 'BGR' channel order) images
|
||||
can be saved using this function, with these exceptions:
|
||||
|
||||
- With OpenEXR encoder, only 32-bit float (CV_32F) images can be saved.
|
||||
- With OpenEXR encoder, only 32-bit float (CV_32F) images can be saved. More than 4 channels can be saved. (imread can load it then.)
|
||||
- 8-bit unsigned (CV_8U) images are not supported.
|
||||
- With Radiance HDR encoder, non 64-bit float (CV_64F) images can be saved.
|
||||
- All images will be converted to 32-bit float (CV_32F).
|
||||
|
||||
@@ -118,7 +118,8 @@ ExrDecoder::ExrDecoder()
|
||||
m_ischroma = false;
|
||||
m_hasalpha = false;
|
||||
m_native_depth = false;
|
||||
|
||||
m_multispectral = false;
|
||||
m_channels = 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -140,7 +141,7 @@ void ExrDecoder::close()
|
||||
|
||||
int ExrDecoder::type() const
|
||||
{
|
||||
return CV_MAKETYPE((m_isfloat ? CV_32F : CV_32S), ((m_iscolor && m_hasalpha) ? 4 : m_iscolor ? 3 : m_hasalpha ? 2 : 1));
|
||||
return CV_MAKETYPE((m_isfloat ? CV_32F : CV_32S), (m_multispectral ? m_channels : (m_iscolor && m_hasalpha) ? 4 : m_iscolor ? 3 : m_hasalpha ? 2 : 1));
|
||||
}
|
||||
|
||||
|
||||
@@ -169,6 +170,7 @@ bool ExrDecoder::readHeader()
|
||||
m_green = channels.findChannel( "G" );
|
||||
m_blue = channels.findChannel( "B" );
|
||||
m_alpha = channels.findChannel( "A" );
|
||||
m_multispectral = channels.findChannel( "0" ) != nullptr;
|
||||
|
||||
if( m_alpha ) // alpha channel supported in RGB, Y, and YC scenarios
|
||||
m_hasalpha = true;
|
||||
@@ -179,6 +181,23 @@ bool ExrDecoder::readHeader()
|
||||
m_ischroma = false;
|
||||
result = true;
|
||||
}
|
||||
else if( m_multispectral )
|
||||
{
|
||||
m_channels = 0;
|
||||
for( auto it = channels.begin(); it != channels.end(); it++ )
|
||||
m_channels++;
|
||||
|
||||
m_iscolor = true; // ??? false
|
||||
m_ischroma = false;
|
||||
m_hasalpha = false;
|
||||
result = m_channels <= CV_CN_MAX;
|
||||
|
||||
for ( int i = 1; result && i < m_channels; i++ ) // channel 0 was found previously
|
||||
{
|
||||
const Channel *ch = channels.findChannel( std::to_string(i) );
|
||||
result = ch && ch->xSampling == 1 && ch->ySampling == 1; // subsampling is not supported
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_green = channels.findChannel( "Y" );
|
||||
@@ -214,8 +233,9 @@ bool ExrDecoder::readHeader()
|
||||
bool ExrDecoder::readData( Mat& img )
|
||||
{
|
||||
m_native_depth = CV_MAT_DEPTH(type()) == img.depth();
|
||||
bool multispectral = img.channels() > 4;
|
||||
bool color = img.channels() > 2; // output mat has 3+ channels; Y or YA are the 1 and 2 channel scenario
|
||||
bool alphasupported = ( img.channels() % 2 == 0 ); // even number of channels indicates alpha
|
||||
bool alphasupported = !multispectral && ( img.channels() % 2 == 0 ); // even number of channels indicates alpha
|
||||
int channels = 0;
|
||||
uchar* data = img.ptr();
|
||||
size_t step = img.step;
|
||||
@@ -231,10 +251,17 @@ bool ExrDecoder::readData( Mat& img )
|
||||
const size_t floatsize = sizeof(float);
|
||||
size_t xstep = m_native_depth ? floatsize : 1; // 4 bytes if native depth (FLOAT), otherwise converting to 1 byte U8 depth
|
||||
size_t ystep = 0;
|
||||
const int channelstoread = ( (m_iscolor && alphasupported) ? 4 :
|
||||
const int channelstoread = ( multispectral ? img.channels() : (m_iscolor && alphasupported) ? 4 :
|
||||
( (m_iscolor && !m_ischroma) || color) ? 3 : alphasupported ? 2 : 1 ); // number of channels to read may exceed channels in output img
|
||||
size_t xStride = floatsize * channelstoread;
|
||||
|
||||
if ( m_multispectral ) // possible gray/RGB conversions
|
||||
{
|
||||
CV_CheckChannelsEQ(img.channels(), CV_MAT_CN(type()), "OpenCV EXR decoder needs more number of channels for multispectral images. Use cv::IMREAD_UNCHANGED mode for imread."); // IMREAD_ANYCOLOR needed
|
||||
CV_CheckDepthEQ(img.depth(), CV_MAT_DEPTH(type()), "OpenCV EXR decoder supports CV_32F depth only for multispectral images. Use cv::IMREAD_UNCHANGED mode for imread."); // IMREAD_ANYDEPTH needed
|
||||
}
|
||||
CV_Assert( multispectral == m_multispectral && (!multispectral || justcopy) ); // should be true after previous checks
|
||||
|
||||
// See https://github.com/opencv/opencv/issues/26705
|
||||
// If ALGO_HINT_ACCURATE is set, read BGR and swap to RGB.
|
||||
// If ALGO_HINT_APPROX is set, read RGB directly.
|
||||
@@ -312,6 +339,15 @@ bool ExrDecoder::readData( Mat& img )
|
||||
xsample[0] = m_green->xSampling;
|
||||
}
|
||||
}
|
||||
else if( m_multispectral )
|
||||
{
|
||||
for ( int i = 0; i < m_channels; i++ )
|
||||
{
|
||||
frame.insert( std::to_string(i), Slice( m_type,
|
||||
buffer - m_datawindow.min.x * xStride - m_datawindow.min.y * ystep + (floatsize * i),
|
||||
xStride, ystep, 1, 1, 0.0 ));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( m_blue )
|
||||
@@ -382,39 +418,42 @@ bool ExrDecoder::readData( Mat& img )
|
||||
{
|
||||
m_file->readPixels( m_datawindow.min.y, m_datawindow.max.y );
|
||||
|
||||
if( m_iscolor )
|
||||
if( !m_multispectral )
|
||||
{
|
||||
if (doReadRGB)
|
||||
if( m_iscolor )
|
||||
{
|
||||
if( m_red && (m_red->xSampling != 1 || m_red->ySampling != 1) )
|
||||
UpSample( data, channelstoread, step / xstep, m_red->xSampling, m_red->ySampling );
|
||||
if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) )
|
||||
UpSample( data + xstep, channelstoread, step / xstep, m_green->xSampling, m_green->ySampling );
|
||||
if( m_blue && (m_blue->xSampling != 1 || m_blue->ySampling != 1) )
|
||||
UpSample( data + 2 * xstep, channelstoread, step / xstep, m_blue->xSampling, m_blue->ySampling );
|
||||
if (doReadRGB)
|
||||
{
|
||||
if( m_red && (m_red->xSampling != 1 || m_red->ySampling != 1) )
|
||||
UpSample( data, channelstoread, step / xstep, m_red->xSampling, m_red->ySampling );
|
||||
if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) )
|
||||
UpSample( data + xstep, channelstoread, step / xstep, m_green->xSampling, m_green->ySampling );
|
||||
if( m_blue && (m_blue->xSampling != 1 || m_blue->ySampling != 1) )
|
||||
UpSample( data + 2 * xstep, channelstoread, step / xstep, m_blue->xSampling, m_blue->ySampling );
|
||||
}
|
||||
else
|
||||
{
|
||||
if( m_blue && (m_blue->xSampling != 1 || m_blue->ySampling != 1) )
|
||||
UpSample( data, channelstoread, step / xstep, m_blue->xSampling, m_blue->ySampling );
|
||||
if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) )
|
||||
UpSample( data + xstep, channelstoread, step / xstep, m_green->xSampling, m_green->ySampling );
|
||||
if( m_red && (m_red->xSampling != 1 || m_red->ySampling != 1) )
|
||||
UpSample( data + 2 * xstep, channelstoread, step / xstep, m_red->xSampling, m_red->ySampling );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( m_blue && (m_blue->xSampling != 1 || m_blue->ySampling != 1) )
|
||||
UpSample( data, channelstoread, step / xstep, m_blue->xSampling, m_blue->ySampling );
|
||||
if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) )
|
||||
UpSample( data + xstep, channelstoread, step / xstep, m_green->xSampling, m_green->ySampling );
|
||||
if( m_red && (m_red->xSampling != 1 || m_red->ySampling != 1) )
|
||||
UpSample( data + 2 * xstep, channelstoread, step / xstep, m_red->xSampling, m_red->ySampling );
|
||||
}
|
||||
}
|
||||
else if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) )
|
||||
UpSample( data, channelstoread, step / xstep, m_green->xSampling, m_green->ySampling );
|
||||
else if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) )
|
||||
UpSample( data, channelstoread, step / xstep, m_green->xSampling, m_green->ySampling );
|
||||
|
||||
if( chromatorgb )
|
||||
{
|
||||
if (doReadRGB)
|
||||
ChromaToRGB( (float *)data, m_height, channelstoread, step / xstep );
|
||||
else
|
||||
ChromaToBGR( (float *)data, m_height, channelstoread, step / xstep );
|
||||
if( chromatorgb )
|
||||
{
|
||||
if (doReadRGB)
|
||||
ChromaToRGB( (float *)data, m_height, channelstoread, step / xstep );
|
||||
else
|
||||
ChromaToBGR( (float *)data, m_height, channelstoread, step / xstep );
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
else // m_multispectral should be false
|
||||
{
|
||||
uchar *out = data;
|
||||
int x, y;
|
||||
@@ -804,13 +843,19 @@ bool ExrEncoder::write( const Mat& img, const std::vector<int>& params )
|
||||
header.channels().insert( "B", Channel( type ) );
|
||||
//printf("bunt\n");
|
||||
}
|
||||
else
|
||||
else if( channels == 1 || channels == 2 )
|
||||
{
|
||||
header.channels().insert( "Y", Channel( type ) );
|
||||
//printf("gray\n");
|
||||
}
|
||||
else if( channels > 4 )
|
||||
{
|
||||
for ( int i = 0; i < channels; i++ )
|
||||
header.channels().insert( std::to_string(i), Channel( type ) );
|
||||
//printf("multi-channel\n");
|
||||
}
|
||||
|
||||
if( channels % 2 == 0 )
|
||||
if( channels % 2 == 0 && channels <= 4)
|
||||
{ // even number of channels indicates Alpha
|
||||
header.channels().insert( "A", Channel( type ) );
|
||||
}
|
||||
@@ -843,10 +888,15 @@ bool ExrEncoder::write( const Mat& img, const std::vector<int>& params )
|
||||
frame.insert( "G", Slice( type, buffer + size, size * channels, bufferstep ));
|
||||
frame.insert( "R", Slice( type, buffer + size * 2, size * channels, bufferstep ));
|
||||
}
|
||||
else
|
||||
else if( channels == 1 || channels == 2 )
|
||||
frame.insert( "Y", Slice( type, buffer, size * channels, bufferstep ));
|
||||
else if( channels > 4 )
|
||||
{
|
||||
for ( int i = 0; i < channels; i++ )
|
||||
frame.insert( std::to_string(i), Slice( type, buffer + size * i, size * channels, bufferstep ));
|
||||
}
|
||||
|
||||
if( channels % 2 == 0 )
|
||||
if( channels % 2 == 0 && channels <= 4 )
|
||||
{ // even channel count indicates Alpha channel
|
||||
frame.insert( "A", Slice( type, buffer + size * (channels - 1), size * channels, bufferstep ));
|
||||
}
|
||||
|
||||
@@ -100,6 +100,8 @@ protected:
|
||||
bool m_iscolor;
|
||||
bool m_isfloat;
|
||||
bool m_hasalpha;
|
||||
bool m_multispectral;
|
||||
int m_channels;
|
||||
|
||||
private:
|
||||
ExrDecoder(const ExrDecoder &); // copy disabled
|
||||
|
||||
@@ -98,6 +98,9 @@ static inline int calcType(int type, int flags)
|
||||
if( (flags & IMREAD_ANYDEPTH) == 0 )
|
||||
type = CV_MAKETYPE(CV_8U, CV_MAT_CN(type));
|
||||
|
||||
//if( (flags & IMREAD_ANYCOLOR) != 0 /*&& CV_MAT_CN(type) > 1*/ )
|
||||
// type = CV_MAKETYPE(CV_MAT_DEPTH(type), CV_MAT_CN(type));
|
||||
//else if( (flags & IMREAD_COLOR) != 0 || (flags & IMREAD_COLOR_RGB) != 0 )
|
||||
if( (flags & IMREAD_COLOR) != 0 || (flags & IMREAD_COLOR_RGB) != 0 ||
|
||||
((flags & IMREAD_ANYCOLOR) != 0 && CV_MAT_CN(type) > 1) )
|
||||
type = CV_MAKETYPE(CV_MAT_DEPTH(type), 3);
|
||||
@@ -1055,7 +1058,12 @@ static bool imwrite_( const String& filename, const std::vector<Mat>& img_vec,
|
||||
Mat image = img_vec[page];
|
||||
CV_Assert(!image.empty());
|
||||
|
||||
#ifdef HAVE_OPENEXR
|
||||
CV_Assert( image.channels() == 1 || image.channels() == 3 || image.channels() == 4 || encoder.dynamicCast<ExrEncoder>() );
|
||||
#else
|
||||
CV_Assert( image.channels() == 1 || image.channels() == 3 || image.channels() == 4 );
|
||||
#endif
|
||||
|
||||
|
||||
Mat temp;
|
||||
if( !encoder->isFormatSupported(image.depth()) )
|
||||
@@ -1609,7 +1617,11 @@ bool imencodeWithMetadata( const String& ext, InputArray _img,
|
||||
CV_Assert(!image.empty());
|
||||
|
||||
const int channels = image.channels();
|
||||
#ifdef HAVE_OPENEXR
|
||||
CV_Assert( channels == 1 || channels == 3 || channels == 4 || encoder.dynamicCast<ExrEncoder>() );
|
||||
#else
|
||||
CV_Assert( channels == 1 || channels == 3 || channels == 4 );
|
||||
#endif
|
||||
|
||||
Mat temp;
|
||||
if( !encoder->isFormatSupported(image.depth()) )
|
||||
|
||||
@@ -68,6 +68,36 @@ TEST(Imgcodecs_EXR, readWrite_32FC3)
|
||||
EXPECT_EQ(0, remove(filenameOutput.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_EXR, readWrite_32FC7)
|
||||
{ // 0-6 channels (multispectral)
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test32FC7.exr";
|
||||
const string filenameOutput = cv::tempfile(".exr");
|
||||
#ifndef GENERATE_DATA
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_UNCHANGED);
|
||||
#else
|
||||
const Size sz(3, 5);
|
||||
Mat img(sz, CV_32FC7);
|
||||
img.at<cv::Vec<float, 7>>(0, 0)[0] = 101.125;
|
||||
img.at<cv::Vec<float, 7>>(2, 1)[3] = 203.500;
|
||||
img.at<cv::Vec<float, 7>>(4, 2)[6] = 305.875;
|
||||
ASSERT_TRUE(cv::imwrite(filenameInput, img));
|
||||
#endif
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_MAKETYPE(CV_32F, 7), img.type());
|
||||
|
||||
ASSERT_TRUE(cv::imwrite(filenameOutput, img));
|
||||
const Mat img2 = cv::imread(filenameOutput, IMREAD_UNCHANGED);
|
||||
EXPECT_EQ(img2.type(), img.type());
|
||||
EXPECT_EQ(img2.size(), img.size());
|
||||
EXPECT_LE(cvtest::norm(img, img2, NORM_INF | NORM_RELATIVE), 1e-3);
|
||||
EXPECT_EQ(0, remove(filenameOutput.c_str()));
|
||||
const Mat img3 = cv::imread(filenameInput, IMREAD_GRAYSCALE);
|
||||
ASSERT_TRUE(img3.empty());
|
||||
const Mat img4 = cv::imread(filenameInput, IMREAD_COLOR);
|
||||
ASSERT_TRUE(img4.empty());
|
||||
}
|
||||
|
||||
|
||||
TEST(Imgcodecs_EXR, readWrite_32FC1_half)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user