Merge pull request #22064 from Kumataro:3.4-fix22052
authorKumataro <Kumataro@users.noreply.github.com>
Mon, 20 Jun 2022 18:42:50 +0000 (03:42 +0900)
committerGitHub <noreply@github.com>
Mon, 20 Jun 2022 18:42:50 +0000 (21:42 +0300)
* 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

modules/imgcodecs/include/opencv2/imgcodecs.hpp
modules/imgcodecs/src/grfmt_jpeg.cpp
modules/imgcodecs/test/test_jpeg.cpp
samples/cpp/imgcodecs_jpeg.cpp [new file with mode: 0644]

index ba63e9934bae5ec70809275aba2353bc31e35b1c..49f76826b98a77a5dd05bae7a9db1721a77118f4 100644 (file)
@@ -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.
 
index d9e056f1a82c50939b5ef79036b8a46cb381cb4e..9500e196f5140effdd12bd1871b804508e492b33 100644 (file)
@@ -44,6 +44,8 @@
 
 #ifdef HAVE_JPEG
 
+#include <opencv2/core/utils/logger.hpp>
+
 #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<int>& 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<int>& 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<uint32_t>(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<int>& 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)
         {
index 9b516a9aba302312605593f5fe39575cb010dcf8..b7932a00200deb2e3f8749d995e341dc11d17028 100644 (file)
@@ -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<uint32_t>(0x221111);
+
+static uint32_t test_jpeg_subsampling( const Mat src, const vector<int> param )
+{
+    vector<uint8_t> 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<int>( 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<int> 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<int> 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<int> 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 (file)
index 0000000..b3abc49
--- /dev/null
@@ -0,0 +1,82 @@
+#include <opencv2/core.hpp>
+#include <opencv2/imgproc.hpp>
+#include <opencv2/imgcodecs.hpp>
+#include <iostream>
+#include <vector>
+
+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<int> param;
+            param.push_back( IMWRITE_JPEG_SAMPLING_FACTOR );
+            param.push_back( config[i].sampling_factor );
+            vector<uint8_t> 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;
+}