C++ – JPEG Compression using OpenCV

cimageimage-compressionjpegopencv

I'm trying to perform a basic JPEG Compression (DCT + quantization + IDCT) using OpenCV not using entropy-encoding/Huffman-coding. The problem is that after I decompress the compressed image, it is not even close in appearance to the original one.

I'm following these tutorials:

Basic JPEG Compressing/Decompressing Simulation

Basic JPEG Compression using OpenCV

Following are the 3 images (original, compressed and decompressed images):
Original Image

Compressed Image

Final Image

I'm using the following matrix to luminance and chrominance:

double dataLuminance[8][8] = {
    {16, 11, 10, 16, 24, 40, 51, 61},
    {12, 12, 14, 19, 26, 58, 60, 55},
    {14, 13, 16, 24, 40, 57, 69, 56},
    {14, 17, 22, 29, 51, 87, 80, 62},
    {18, 22, 37, 56, 68, 109, 103, 77},
    {24, 35, 55, 64, 81, 104, 113, 92},
    {49, 64, 78, 87, 103, 121, 120, 101},
    {72, 92, 95, 98, 112, 100, 103, 99}
};

double dataChrominance[8][8] = {
    {17, 18, 24, 27, 99, 99, 99, 99},
    {18, 21, 26, 66, 99, 99, 99, 99},
    {24, 26, 56, 99, 99, 99, 99, 99},
    {47, 66, 99, 99, 99, 99, 99, 99},
    {99, 99, 99, 99, 99, 99, 99, 99},
    {99, 99, 99, 99, 99, 99, 99, 99},
    {99, 99, 99, 99, 99, 99, 99, 99},
    {99, 99, 99, 99, 99, 99, 99, 99}
};

// EDIT 1: @Micka told about the problem of using imread/imwrite, so I edited my code to use the compressed image directly from my program.

The compression method is:

void ImageCompression::compression(){
// Getting original image size
int height = imgOriginal.size().height;
int width = imgOriginal.size().width;

// Converting image color
Mat imgColorConverted;
cvtColor(imgOriginal, imgColorConverted, CV_BGR2YCrCb);

// Transforming 2D Array in Image Matrix
Mat luminance = Mat(8,8, CV_64FC1, &dataLuminance);
Mat chrominance = Mat(8,8, CV_64FC1, &dataChrominance);

cout << "Luminance: " << luminance << endl << endl;
cout << "Chrominance" << chrominance << endl << endl;

// Splitting the image into 3 planes
vector<Mat> planes;
split(imgColorConverted, planes);

// Downsampling chrominance
// Resizing to 1/4 of original image
resize(planes[1], planes[1], Size(width/2, height/2));
resize(planes[2], planes[2], Size(width/2, height/2));

// Resizing to original image size
resize(planes[1], planes[1], Size(width, height));
resize(planes[2], planes[2], Size(width, height));

// Dividing image in blocks 8x8
for ( int i = 0; i < height; i+=8 ){
    for( int j = 0; j < width; j+=8 ){
        // For each plane
        for( int plane = 0; plane < imgColorConverted.channels(); plane++ ){

            // Creating a block
            Mat block = planes[plane](Rect(j, i, 8, 8));

            // Converting the block to float
            block.convertTo( block, CV_64FC1 );

            // Subtracting the block by 128
            subtract( block, 128.0, block );

            // DCT
            dct( block, block );

            // Applying quantization
            if( plane == 0 ){
                divide( block, luminance, block );
            }
            else {
                divide( block, chrominance, block );
            }

            // Converting it back to unsigned int
            block.convertTo( block, CV_8UC1 );

            // Copying the block to the original image
            block.copyTo( planes[plane](Rect(j, i, 8, 8)) );
        }
    }
}

merge( planes, finalImage );
}

And my decompression method:

ImageCompression::decompression{
// Getting the size of the image
int height = finalImage.size().height;
int width = finalImage.size().width;

// Transforming 2D Array in Image Matrix
Mat luminance = Mat(8,8, CV_64FC1, &dataLuminance);
Mat chrominance = Mat(8,8, CV_64FC1, &dataChrominance);

// Splitting the image into 3 planes
vector<Mat> planes;
split(finalImage, planes);

// Dividing the image in blocks 8x8
for ( int i = 0; i < height; i+=8 ){
    for( int j = 0; j < width; j+=8 ){
        // For each plane
        for( int plane = 0; plane < finalImage.channels(); plane++ ){

            // Creating a block
            Mat block = planes[plane](Rect(j, i, 8, 8));

            // Converting the block to float
            block.convertTo( block, CV_64FC1 );

            // Applying dequantization
            if( plane == 0 ){
                multiply( block, luminance, block );
            }
            else {
                multiply( block, chrominance, block );
            }

            // IDCT
            idct( block, block );

            // Adding 128 to the block
            add( block, 128.0, block );

            // Converting it back to unsigned int
            block.convertTo( block, CV_8UC1 );

            // Copying the block to the original image
            block.copyTo( planes[plane](Rect(j, i, 8, 8)) );
        }
    }
}

merge(planes, finalImage);
cvtColor( finalImage, finalImage, CV_YCrCb2BGR );

imshow("Decompressed image", finalImage);
waitKey(0);
imwrite(".../finalResult.jpg", finalImage);
}

Does someone have any idea of why I'm getting that resulting image?

Thank you.

Best Answer

You need to add 128 back to the block before converting it back to unsigned int and then subtract it again in decompression.

            add(block, 128.0, block);

            // Converting it back to unsigned int
            block.convertTo(block, CV_8UC1);

.

        // Converting the block to float
        block.convertTo(block, CV_64FC1);

        subtract(block, 128.0, block);