From dd7b9000ada172fa3c8954a89339dcf6249b8a59 Mon Sep 17 00:00:00 2001 From: Kumataro Date: Tue, 21 Jun 2022 03:42:50 +0900 Subject: [PATCH] Merge pull request #22064 from Kumataro:3.4-fix22052 * imgcodecs: jpeg: add IMWRITE_JPEG_SAMPLING_FACTOR parameter * fix compile error * imgcodecs: jpeg: add CV_LOG_WARNING() and fix how to initilize Mat * imgcodecs: jpeg: fix for C++98 mode. * samples: imgcodec_jpeg: Remove license --- .../imgcodecs/include/opencv2/imgcodecs.hpp | 11 +++ modules/imgcodecs/src/grfmt_jpeg.cpp | 32 +++++++ modules/imgcodecs/test/test_jpeg.cpp | 92 +++++++++++++++++++ samples/cpp/imgcodecs_jpeg.cpp | 82 +++++++++++++++++ 4 files changed, 217 insertions(+) create mode 100644 samples/cpp/imgcodecs_jpeg.cpp diff --git a/modules/imgcodecs/include/opencv2/imgcodecs.hpp b/modules/imgcodecs/include/opencv2/imgcodecs.hpp index ba63e9934b..49f76826b9 100644 --- a/modules/imgcodecs/include/opencv2/imgcodecs.hpp +++ b/modules/imgcodecs/include/opencv2/imgcodecs.hpp @@ -89,6 +89,7 @@ enum ImwriteFlags { IMWRITE_JPEG_RST_INTERVAL = 4, //!< JPEG restart interval, 0 - 65535, default is 0 - no restart. IMWRITE_JPEG_LUMA_QUALITY = 5, //!< Separate luma quality level, 0 - 100, default is -1 - don't use. IMWRITE_JPEG_CHROMA_QUALITY = 6, //!< Separate chroma quality level, 0 - 100, default is -1 - don't use. + IMWRITE_JPEG_SAMPLING_FACTOR = 7, //!< For JPEG, set sampling factor. See cv::ImwriteJPEGSamplingFactorParams. IMWRITE_PNG_COMPRESSION = 16, //!< For PNG, it can be the compression level from 0 to 9. A higher value means a smaller size and longer compression time. If specified, strategy is changed to IMWRITE_PNG_STRATEGY_DEFAULT (Z_DEFAULT_STRATEGY). Default value is 1 (best speed setting). IMWRITE_PNG_STRATEGY = 17, //!< One of cv::ImwritePNGFlags, default is IMWRITE_PNG_STRATEGY_RLE. IMWRITE_PNG_BILEVEL = 18, //!< Binary level PNG, 0 or 1, default is 0. @@ -102,12 +103,22 @@ enum ImwriteFlags { IMWRITE_TIFF_COMPRESSION = 259 //!< For TIFF, use to specify the image compression scheme. See libtiff for integer constants corresponding to compression formats. Note, for images whose depth is CV_32F, only libtiff's SGILOG compression scheme is used. For other supported depths, the compression scheme can be specified by this flag; LZW compression is the default. }; +enum ImwriteJPEGSamplingFactorParams { + IMWRITE_JPEG_SAMPLING_FACTOR_411 = 0x411111, //!< 4x1,1x1,1x1 + IMWRITE_JPEG_SAMPLING_FACTOR_420 = 0x221111, //!< 2x2,1x1,1x1(Default) + IMWRITE_JPEG_SAMPLING_FACTOR_422 = 0x211111, //!< 2x1,1x1,1x1 + IMWRITE_JPEG_SAMPLING_FACTOR_440 = 0x121111, //!< 1x2,1x1,1x1 + IMWRITE_JPEG_SAMPLING_FACTOR_444 = 0x111111 //!< 1x1,1x1,1x1(No subsampling) + }; + + enum ImwriteEXRTypeFlags { /*IMWRITE_EXR_TYPE_UNIT = 0, //!< not supported */ IMWRITE_EXR_TYPE_HALF = 1, //!< store as HALF (FP16) IMWRITE_EXR_TYPE_FLOAT = 2 //!< store as FP32 (default) }; + //! Imwrite PNG specific flags used to tune the compression algorithm. /** These flags will be modify the way of PNG image compression and will be passed to the underlying zlib processing stage. diff --git a/modules/imgcodecs/src/grfmt_jpeg.cpp b/modules/imgcodecs/src/grfmt_jpeg.cpp index d9e056f1a8..9500e196f5 100644 --- a/modules/imgcodecs/src/grfmt_jpeg.cpp +++ b/modules/imgcodecs/src/grfmt_jpeg.cpp @@ -44,6 +44,8 @@ #ifdef HAVE_JPEG +#include + #ifdef _MSC_VER //interaction between '_setjmp' and C++ object destruction is non-portable #pragma warning(disable: 4611) @@ -640,6 +642,7 @@ bool JpegEncoder::write( const Mat& img, const std::vector& params ) int rst_interval = 0; int luma_quality = -1; int chroma_quality = -1; + uint32_t sampling_factor = 0; // same as 0x221111 for( size_t i = 0; i < params.size(); i += 2 ) { @@ -687,6 +690,27 @@ bool JpegEncoder::write( const Mat& img, const std::vector& params ) rst_interval = params[i+1]; rst_interval = MIN(MAX(rst_interval, 0), 65535L); } + + if( params[i] == IMWRITE_JPEG_SAMPLING_FACTOR ) + { + sampling_factor = static_cast(params[i+1]); + + switch ( sampling_factor ) + { + case IMWRITE_JPEG_SAMPLING_FACTOR_411: + case IMWRITE_JPEG_SAMPLING_FACTOR_420: + case IMWRITE_JPEG_SAMPLING_FACTOR_422: + case IMWRITE_JPEG_SAMPLING_FACTOR_440: + case IMWRITE_JPEG_SAMPLING_FACTOR_444: + // OK. + break; + + default: + CV_LOG_WARNING(NULL, cv::format("Unknown value for IMWRITE_JPEG_SAMPLING_FACTOR: 0x%06x", sampling_factor ) ); + sampling_factor = 0; + break; + } + } } jpeg_set_defaults( &cinfo ); @@ -699,6 +723,14 @@ bool JpegEncoder::write( const Mat& img, const std::vector& params ) if( optimize ) cinfo.optimize_coding = TRUE; + if( (channels > 1) && ( sampling_factor != 0 ) ) + { + cinfo.comp_info[0].v_samp_factor = (sampling_factor >> 16 ) & 0xF; + cinfo.comp_info[0].h_samp_factor = (sampling_factor >> 20 ) & 0xF; + cinfo.comp_info[1].v_samp_factor = 1; + cinfo.comp_info[1].h_samp_factor = 1; + } + #if JPEG_LIB_VERSION >= 70 if (luma_quality >= 0 && chroma_quality >= 0) { diff --git a/modules/imgcodecs/test/test_jpeg.cpp b/modules/imgcodecs/test/test_jpeg.cpp index 9b516a9aba..b7932a0020 100644 --- a/modules/imgcodecs/test/test_jpeg.cpp +++ b/modules/imgcodecs/test/test_jpeg.cpp @@ -178,6 +178,98 @@ TEST(Imgcodecs_Jpeg, encode_decode_rst_jpeg) EXPECT_EQ(0, remove(output_normal.c_str())); } +//================================================================================================== + +static const uint32_t default_sampling_factor = static_cast(0x221111); + +static uint32_t test_jpeg_subsampling( const Mat src, const vector param ) +{ + vector jpeg; + + if ( cv::imencode(".jpg", src, jpeg, param ) == false ) + { + return 0; + } + + if ( src.channels() != 3 ) + { + return 0; + } + + // Find SOF Marker(FFC0) + int sof_offset = 0; // not found. + int jpeg_size = static_cast( jpeg.size() ); + for ( int i = 0 ; i < jpeg_size - 1; i++ ) + { + if ( (jpeg[i] == 0xff ) && ( jpeg[i+1] == 0xC0 ) ) + { + sof_offset = i; + break; + } + } + if ( sof_offset == 0 ) + { + return 0; + } + + // Extract Subsampling Factor from SOF. + return ( jpeg[sof_offset + 0x0A + 3 * 0 + 1] << 16 ) + + ( jpeg[sof_offset + 0x0A + 3 * 1 + 1] << 8 ) + + ( jpeg[sof_offset + 0x0A + 3 * 2 + 1] ) ; +} + +TEST(Imgcodecs_Jpeg, encode_subsamplingfactor_default) +{ + vector param; + Mat src( 48, 64, CV_8UC3, cv::Scalar::all(0) ); + EXPECT_EQ( default_sampling_factor, test_jpeg_subsampling(src, param) ); +} + +TEST(Imgcodecs_Jpeg, encode_subsamplingfactor_usersetting_valid) +{ + Mat src( 48, 64, CV_8UC3, cv::Scalar::all(0) ); + const uint32_t sampling_factor_list[] = { + IMWRITE_JPEG_SAMPLING_FACTOR_411, + IMWRITE_JPEG_SAMPLING_FACTOR_420, + IMWRITE_JPEG_SAMPLING_FACTOR_422, + IMWRITE_JPEG_SAMPLING_FACTOR_440, + IMWRITE_JPEG_SAMPLING_FACTOR_444, + }; + const int sampling_factor_list_num = 5; + + for ( int i = 0 ; i < sampling_factor_list_num; i ++ ) + { + vector param; + param.push_back( IMWRITE_JPEG_SAMPLING_FACTOR ); + param.push_back( sampling_factor_list[i] ); + EXPECT_EQ( sampling_factor_list[i], test_jpeg_subsampling(src, param) ); + } +} + +TEST(Imgcodecs_Jpeg, encode_subsamplingfactor_usersetting_invalid) +{ + Mat src( 48, 64, CV_8UC3, cv::Scalar::all(0) ); + const uint32_t sampling_factor_list[] = { // Invalid list + 0x111112, + 0x000000, + 0x001111, + 0xFF1111, + 0x141111, // 1x4,1x1,1x1 - unknown + 0x241111, // 2x4,1x1,1x1 - unknown + 0x421111, // 4x2,1x1,1x1 - unknown + 0x441111, // 4x4,1x1,1x1 - 410(libjpeg cannot handle it) + }; + const int sampling_factor_list_num = 8; + + for ( int i = 0 ; i < sampling_factor_list_num; i ++ ) + { + vector param; + param.push_back( IMWRITE_JPEG_SAMPLING_FACTOR ); + param.push_back( sampling_factor_list[i] ); + EXPECT_EQ( default_sampling_factor, test_jpeg_subsampling(src, param) ); + } +} + #endif // HAVE_JPEG }} // namespace diff --git a/samples/cpp/imgcodecs_jpeg.cpp b/samples/cpp/imgcodecs_jpeg.cpp new file mode 100644 index 0000000000..b3abc49286 --- /dev/null +++ b/samples/cpp/imgcodecs_jpeg.cpp @@ -0,0 +1,82 @@ +#include +#include +#include +#include +#include + +using namespace std; +using namespace cv; + +int main(int /*argc*/, const char** /* argv */ ) +{ + Mat framebuffer( 160 * 2, 160 * 5, CV_8UC3, cv::Scalar::all(255) ); + + Mat img( 160, 160, CV_8UC3, cv::Scalar::all(255) ); + + // Create test image. + { + const Point center( img.rows / 2 , img.cols /2 ); + + for( int radius = 5; radius < img.rows ; radius += 3.5 ) + { + cv::circle( img, center, radius, Scalar(255,0,255) ); + } + cv::rectangle( img, Point(0,0), Point(img.rows-1, img.cols-1), Scalar::all(0), 2 ); + } + + // Draw original image(s). + int top = 0; // Upper images + { + for( int left = 0 ; left < img.rows * 5 ; left += img.rows ){ + Mat roi = framebuffer( Rect( left, top, img.rows, img.cols ) ); + img.copyTo(roi); + + cv::putText( roi, "original", Point(5,15), FONT_HERSHEY_SIMPLEX, 0.5, Scalar::all(0), 2, 4, false ); + } + } + + // Draw lossy images + top += img.cols; // Lower images + { + struct test_config{ + string comment; + uint32_t sampling_factor; + } config [] = { + { "411", IMWRITE_JPEG_SAMPLING_FACTOR_411 }, + { "420", IMWRITE_JPEG_SAMPLING_FACTOR_420 }, + { "422", IMWRITE_JPEG_SAMPLING_FACTOR_422 }, + { "440", IMWRITE_JPEG_SAMPLING_FACTOR_440 }, + { "444", IMWRITE_JPEG_SAMPLING_FACTOR_444 }, + }; + + const int config_num = 5; + + int left = 0; + + for ( int i = 0 ; i < config_num; i++ ) + { + // Compress images with sampling factor parameter. + vector param; + param.push_back( IMWRITE_JPEG_SAMPLING_FACTOR ); + param.push_back( config[i].sampling_factor ); + vector jpeg; + (void) imencode(".jpg", img, jpeg, param ); + + // Decompress it. + Mat jpegMat(jpeg); + Mat lossy_img = imdecode(jpegMat, -1); + + // Copy into framebuffer and comment + Mat roi = framebuffer( Rect( left, top, lossy_img.rows, lossy_img.cols ) ); + lossy_img.copyTo(roi); + cv::putText( roi, config[i].comment, Point(5,155), FONT_HERSHEY_SIMPLEX, 0.5, Scalar::all(0), 2, 4, false ); + + left += lossy_img.rows; + } + } + + // Output framebuffer(as lossless). + imwrite( "imgcodecs_jpeg_samplingfactor_result.png", framebuffer ); + + return 0; +}