Salt-and-pepper noise effect when decoding progressive jpeg in libNvJpg

Hello.

salt-and-pepper noise effect occurs when decoding certain Progressive Jpeg images.

Is there a way to solve this issue?

The version tested is cuda 12.4.

Sample code is enclosed.

#include <iostream>
#include <fstream>
#include <vector>
#include <cstdlib>
#include <nvjpeg.h>
#include <cuda_runtime.h>

// Error handling macros
#define CHECK_CUDA(call) \
    { \
        const cudaError_t error = call; \
        if (error != cudaSuccess) { \
            std::cerr << "CUDA Error: " << __FILE__ << ":" << __LINE__ << ", " \
                      << cudaGetErrorString(error) << std::endl; \
            exit(1); \
        } \
    }

#define CHECK_NVJPEG(call) \
    { \
        const nvjpegStatus_t status = call; \
        if (status != NVJPEG_STATUS_SUCCESS) { \
            std::cerr << "NVJPEG Error: " << __FILE__ << ":" << __LINE__ << ", code: " \
                      << status << std::endl; \
            exit(1); \
        } \
    }

void EncodeJPEG(unsigned char **ycbcr, int width, int height, const std::string &output_filename) {
    nvjpegHandle_t nvjpeg_handle;
    nvjpegEncoderState_t nvjpeg_state;
    nvjpegEncoderParams_t nvjpeg_params;
    nvjpegImage_t nvjpeg_image;

    CHECK_NVJPEG(nvjpegCreateSimple(&nvjpeg_handle));
    CHECK_NVJPEG(nvjpegEncoderStateCreate(nvjpeg_handle, &nvjpeg_state, NULL));
    CHECK_NVJPEG(nvjpegEncoderParamsCreate(nvjpeg_handle, &nvjpeg_params, NULL));

    nvjpeg_image.channel[0] = ycbcr[0];
    nvjpeg_image.channel[1] = ycbcr[1];
    nvjpeg_image.channel[2] = ycbcr[2];
    nvjpeg_image.pitch[0] = width;
    nvjpeg_image.pitch[1] = width / 2;
    nvjpeg_image.pitch[2] = width / 2;

    CHECK_NVJPEG(nvjpegEncoderParamsSetSamplingFactors(nvjpeg_params, NVJPEG_CSS_420, NULL));
    CHECK_NVJPEG(nvjpegEncodeYUV(nvjpeg_handle,
                                 nvjpeg_state,
                                 nvjpeg_params,
                                 &nvjpeg_image,
                                 NVJPEG_CSS_420,
                                 width,
                                 height,
                                 NULL));

    // Retrieve encoded JPEG
    std::vector<unsigned char> jpeg_buffer;
    size_t jpeg_size;
    CHECK_NVJPEG(nvjpegEncodeRetrieveBitstream(nvjpeg_handle, nvjpeg_state, NULL, &jpeg_size, NULL));
    jpeg_buffer.resize(jpeg_size);
    CHECK_NVJPEG(nvjpegEncodeRetrieveBitstream(nvjpeg_handle, nvjpeg_state, jpeg_buffer.data(), &jpeg_size, NULL));

    // Save JPEG to file
    std::ofstream output_file(output_filename.c_str(), std::ios::binary);
    output_file.write(reinterpret_cast<char *>(jpeg_buffer.data()), jpeg_size);
    output_file.close();

    // Clean up
    nvjpegEncoderStateDestroy(nvjpeg_state);
    nvjpegEncoderParamsDestroy(nvjpeg_params);
    nvjpegDestroy(nvjpeg_handle);
}

void DecodeJPEG(const std::string &input_filename, unsigned char **ycbcr, int &width, int &height) {
    nvjpegHandle_t nvjpeg_handle;
    nvjpegJpegState_t jpeg_state;

    CHECK_NVJPEG(nvjpegCreateSimple(&nvjpeg_handle));
    CHECK_NVJPEG(nvjpegJpegStateCreate(nvjpeg_handle, &jpeg_state));

    // Load JPEG image into memory
    std::ifstream file(input_filename.c_str(), std::ios::binary);
    if (!file) {
        std::cerr << "Cannot open file: " << input_filename.c_str() << std::endl;
        exit(1);
    }

    file.seekg(0, std::ios::end);
    size_t file_size = file.tellg();
    file.seekg(0, std::ios::beg);

    std::vector<unsigned char> jpeg_data(file_size);
    file.read(reinterpret_cast<char *>(jpeg_data.data()), file_size);
    file.close();

    // Decode JPEG image
    int nComponents;
    nvjpegChromaSubsampling_t subsampling;
    int widths[NVJPEG_MAX_COMPONENT];
    int heights[NVJPEG_MAX_COMPONENT];

    CHECK_NVJPEG(nvjpegGetImageInfo(nvjpeg_handle,
                                    jpeg_data.data(),
                                    jpeg_data.size(),
                                    &nComponents,
                                    &subsampling,
                                    widths,
                                    heights));

    width = widths[0];
    height = heights[0];

    nvjpegImage_t nvjpeg_image;
    for (int i = 0; i < nComponents; i++) {
        CHECK_CUDA(cudaMalloc(&ycbcr[i], widths[i] * heights[i]));
        cudaMemset(ycbcr[i], 0, widths[i] * heights[i]);
        nvjpeg_image.channel[i] = ycbcr[i];
        nvjpeg_image.pitch[i] = widths[i];
    }

    CHECK_NVJPEG(nvjpegDecode(nvjpeg_handle,
                              jpeg_state,
                              jpeg_data.data(),
                              jpeg_data.size(),
                              NVJPEG_OUTPUT_YUV,
                              &nvjpeg_image,
                              0));

    // Cleanup
    nvjpegJpegStateDestroy(jpeg_state);
    nvjpegDestroy(nvjpeg_handle);
}

int main() {
    std::vector<std::string> input_filenames;
    input_filenames.push_back("papernoise_1080x1080_yuvj420p_progressive.jpg");
    input_filenames.push_back("papernoise_1264x843_yuvj420p_progressive.jpg");
    input_filenames.push_back("papernoise_1080x1350_yuvj420p_progressive.jpg");

    std::vector<std::string> output_filenames;
    output_filenames.push_back("papernoise_1080x1080_yuvj420p_progressive_out.jpg");
    output_filenames.push_back("papernoise_1264x843_yuvj420p_progressive_out.jpg");
    output_filenames.push_back("papernoise_1080x1350_yuvj420p_progressive_out.jpg");

    unsigned char *ycbcr[3];
    int width, height;

    for (int i = 0; i < input_filenames.size(); i++) {
        // Decode the progressive JPEG
        DecodeJPEG(input_filenames[i], ycbcr, width, height);

        // Encode the decoded image back to JPEG
        EncodeJPEG(ycbcr, width, height, output_filenames[i]);

        // Clean up
        for (int i = 0; i < 3; i++) {
            CHECK_CUDA(cudaFree(ycbcr[i]));
        }
    }

    return 0;
}

jpeg_to_jpeg.cpp


export PATH=/usr/local/cuda/bin:$PATH
g++ -o jpeg_to_jpeg jpeg_to_jpeg.cpp -I/usr/local/cuda-12.3/include -L/usr/local/cuda-12.3/lib64 -lgif -lnvjpeg -lnppicc -lnppig -lnppc -lcuda -lcudart
./jpeg_to_jpeg

execute.sh



3 sample images