Merge pull request #26872 from sturkmen72:ImageEncoders_revisions

Performance tests for image encoders and decoders and code cleanup #26872

### 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
- [ ] 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:
Suleyman TURKMEN 2025-02-04 12:21:55 +03:00 committed by GitHub
parent 59c3b6c995
commit e8e49ab7a8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 155 additions and 26 deletions

View File

@ -2170,9 +2170,15 @@ static void showSaveDialog(CvWindow& window)
#ifdef HAVE_WEBP
"WebP files (*.webp)\0*.webp\0"
#endif
"Portable image format (*.pbm;*.pgm;*.ppm;*.pxm;*.pnm)\0*.pbm;*.pgm;*.ppm;*.pxm;*.pnm\0"
"Portable image format (*.pbm;*.pgm;*.ppm;*.pnm;*.pam)\0*.pbm;*.pgm;*.ppm;*.pnm;*.pam\0"
#ifdef HAVE_OPENEXR
"OpenEXR Image files (*.exr)\0*.exr\0"
#endif
#ifdef HAVE_AVIF
"AVIF files (*.avif)\0*.avif\0"
#endif
#ifdef HAVE_IMGCODEC_GIF
"Graphics Interchange Format 89a(*.gif)\0*.gif\0"
#endif
"Radiance HDR (*.hdr;*.pic)\0*.hdr;*.pic\0"
"Sun raster files (*.sr;*.ras)\0*.sr;*.ras\0"
@ -2194,7 +2200,7 @@ static void showSaveDialog(CvWindow& window)
}
#else
CV_UNUSED(window);
CV_LOG_WARNING("Save dialog requires enabled 'imgcodecs' module.");
CV_LOG_WARNING(NULL, "Save dialog requires enabled 'imgcodecs' module.");
return;
#endif
}

View File

@ -0,0 +1,131 @@
// 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 "perf_precomp.hpp"
namespace opencv_test
{
#ifdef HAVE_PNG
using namespace perf;
typedef perf::TestBaseWithParam<std::string> Decode;
typedef perf::TestBaseWithParam<std::string> Encode;
const string exts[] = {
#ifdef HAVE_AVIF
".avif",
#endif
".bmp",
#ifdef HAVE_IMGCODEC_GIF
".gif",
#endif
#if (defined(HAVE_JASPER) && defined(OPENCV_IMGCODECS_ENABLE_JASPER_TESTS)) \
|| defined(HAVE_OPENJPEG)
".jp2",
#endif
#ifdef HAVE_JPEG
".jpg",
#endif
#ifdef HAVE_JPEGXL
".jxl",
#endif
".png",
#ifdef HAVE_IMGCODEC_PXM
".ppm",
#endif
#ifdef HAVE_IMGCODEC_SUNRASTER
".ras",
#endif
#ifdef HAVE_TIFF
".tiff",
#endif
#ifdef HAVE_WEBP
".webp",
#endif
};
const string exts_multi[] = {
#ifdef HAVE_AVIF
".avif",
#endif
#ifdef HAVE_IMGCODEC_GIF
".gif",
#endif
".png",
#ifdef HAVE_TIFF
".tiff",
#endif
#ifdef HAVE_WEBP
".webp",
#endif
};
PERF_TEST_P(Decode, bgr, testing::ValuesIn(exts))
{
String filename = getDataPath("perf/1920x1080.png");
Mat src = imread(filename);
EXPECT_FALSE(src.empty()) << "Cannot open test image perf/1920x1080.png";
vector<uchar> buf;
EXPECT_TRUE(imencode(GetParam(), src, buf));
TEST_CYCLE() imdecode(buf, IMREAD_UNCHANGED);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P(Decode, rgb, testing::ValuesIn(exts))
{
String filename = getDataPath("perf/1920x1080.png");
Mat src = imread(filename);
EXPECT_FALSE(src.empty()) << "Cannot open test image perf/1920x1080.png";
vector<uchar> buf;
EXPECT_TRUE(imencode(GetParam(), src, buf));
TEST_CYCLE() imdecode(buf, IMREAD_COLOR_RGB);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P(Encode, bgr, testing::ValuesIn(exts))
{
String filename = getDataPath("perf/1920x1080.png");
Mat src = imread(filename);
EXPECT_FALSE(src.empty()) << "Cannot open test image perf/1920x1080.png";
vector<uchar> buf;
TEST_CYCLE() imencode(GetParam(), src, buf);
std::cout << "Encoded buffer size: " << buf.size()
<< " bytes, Compression ratio: " << std::fixed << std::setprecision(2)
<< (static_cast<double>(buf.size()) / (src.total() * src.channels())) * 100.0 << "%" << std::endl;
SANITY_CHECK_NOTHING();
}
PERF_TEST_P(Encode, multi, testing::ValuesIn(exts_multi))
{
String filename = getDataPath("perf/1920x1080.png");
vector<Mat> vec;
EXPECT_TRUE(imreadmulti(filename, vec));
vec.push_back(vec.back().clone());
circle(vec.back(), Point(100, 100), 45, Scalar(0, 0, 255, 0), 2, LINE_AA);
vector<uchar> buf;
EXPECT_TRUE(imwrite("test" + GetParam(), vec));
TEST_CYCLE() imencode(GetParam(), vec, buf);
std::cout << "Encoded buffer size: " << buf.size()
<< " bytes, Compression ratio: " << std::fixed << std::setprecision(2)
<< (static_cast<double>(buf.size()) / (vec[0].total() * vec[0].channels())) * 100.0 << "%" << std::endl;
SANITY_CHECK_NOTHING();
}
#endif // HAVE_PNG
} // namespace

View File

@ -298,11 +298,6 @@ bool AvifEncoder::isFormatSupported(int depth) const {
return (depth == CV_8U || depth == CV_16U);
}
bool AvifEncoder::write(const Mat &img, const std::vector<int> &params) {
std::vector<Mat> img_vec(1, img);
return writemulti(img_vec, params);
}
bool AvifEncoder::writeanimation(const Animation& animation,
const std::vector<int> &params) {
int bit_depth = 8;

View File

@ -41,7 +41,6 @@ class AvifEncoder CV_FINAL : public BaseImageEncoder {
~AvifEncoder() CV_OVERRIDE;
bool isFormatSupported(int depth) const CV_OVERRIDE;
bool write(const Mat& img, const std::vector<int>& params) CV_OVERRIDE;
bool writeanimation(const Animation& animation, const std::vector<int>& params) CV_OVERRIDE;
ImageEncoder newEncoder() const CV_OVERRIDE;

View File

@ -140,6 +140,11 @@ bool BaseImageEncoder::setDestination( std::vector<uchar>& buf )
return true;
}
bool BaseImageEncoder::write(const Mat &img, const std::vector<int> &params) {
std::vector<Mat> img_vec(1, img);
return writemulti(img_vec, params);
}
bool BaseImageEncoder::writemulti(const std::vector<Mat>& img_vec, const std::vector<int>& params)
{
if(img_vec.size() > 1)
@ -157,6 +162,7 @@ bool BaseImageEncoder::writemulti(const std::vector<Mat>& img_vec, const std::ve
bool BaseImageEncoder::writeanimation(const Animation&, const std::vector<int>& )
{
CV_LOG_WARNING(NULL, "No Animation encoder for specified file extension");
return false;
}
@ -165,7 +171,7 @@ ImageEncoder BaseImageEncoder::newEncoder() const
return ImageEncoder();
}
void BaseImageEncoder::throwOnEror() const
void BaseImageEncoder::throwOnError() const
{
if(!m_last_error.empty())
{

View File

@ -202,12 +202,11 @@ public:
/**
* @brief Encode and write the image data.
* This is a pure virtual function that must be implemented by derived classes.
* @param img The Mat object containing the image data to be encoded.
* @param params A vector of parameters controlling the encoding process (e.g., compression level).
* @return true if the image was successfully written, false otherwise.
*/
virtual bool write(const Mat& img, const std::vector<int>& params) = 0;
virtual bool write(const Mat& img, const std::vector<int>& params);
/**
* @brief Encode and write multiple images (e.g., for animated formats).
@ -236,7 +235,7 @@ public:
* @brief Throw an exception based on the last error encountered during encoding.
* This method can be used to propagate error conditions back to the caller.
*/
virtual void throwOnEror() const;
virtual void throwOnError() const;
protected:
String m_description; ///< Description of the encoder (e.g., format name, capabilities).

View File

@ -514,19 +514,11 @@ GifEncoder::~GifEncoder() {
close();
}
bool GifEncoder::isFormatSupported(int depth) const {
return depth == CV_8U;
}
bool GifEncoder::write(const Mat &img, const std::vector<int> &params) {
std::vector<Mat> img_vec(1, img);
return writemulti(img_vec, params);
}
bool GifEncoder::writeanimation(const Animation& animation, const std::vector<int>& params) {
if (animation.frames.empty()) {
return false;
}
CV_CheckDepthEQ(animation.frames[0].depth(), CV_8U, "GIF encoder supports only 8-bit unsigned images");
if (m_buf) {
if (!strm.open(*m_buf)) {

View File

@ -83,9 +83,6 @@ public:
GifEncoder();
~GifEncoder() CV_OVERRIDE;
bool isFormatSupported(int depth) const CV_OVERRIDE;
bool write(const Mat& img, const std::vector<int>& params) CV_OVERRIDE;
bool writeanimation(const Animation& animation, const std::vector<int>& params) CV_OVERRIDE;
ImageEncoder newEncoder() const CV_OVERRIDE;

View File

@ -1412,6 +1412,9 @@ void PngEncoder::deflateRectFin(unsigned char* zbuf, uint32_t* zsize, int bpp, i
bool PngEncoder::writeanimation(const Animation& animation, const std::vector<int>& params)
{
int frame_type = animation.frames[0].type();
int frame_depth = animation.frames[0].depth();
CV_CheckType(frame_type, frame_depth == CV_8U || frame_depth == CV_16U, "APNG decoder supports only 8 or 16 bit unsigned images");
int compression_level = 6;
int compression_strategy = IMWRITE_PNG_STRATEGY_RLE; // Default strategy
bool isBilevel = false;
@ -1435,7 +1438,8 @@ bool PngEncoder::writeanimation(const Animation& animation, const std::vector<in
}
}
CV_UNUSED(isBilevel);
if (isBilevel)
CV_LOG_WARNING(NULL, "IMWRITE_PNG_BILEVEL parameter is not supported yet.");
uint32_t first =0;
uint32_t loops= animation.loop_count;
uint32_t coltype= animation.frames[0].channels() == 1 ? PNG_COLOR_TYPE_GRAY : animation.frames[0].channels() == 3 ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;

View File

@ -1372,7 +1372,7 @@ bool imencode( const String& ext, InputArray _img,
else
code = encoder->writemulti(write_vec, params);
encoder->throwOnEror();
encoder->throwOnError();
CV_Assert( code );
}
catch (const cv::Exception& e)