8 位到 10 位图像转换的位移和缩放之间的结果差异

问题描述 投票:0回答:1

我正在开发一个项目,涉及使用 C++ 中的 OpenCV 将图像从 8 位深度转换为 10 位深度。我已经实现了两种转换方法:位移和缩放。但是,我观察到这两种方法之间的图像质量存在差异,并且我不确定哪一种是准确转换的正确方法。

代码片段:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

// Constants for bit depth conversion
const int BIT_SHIFT = 2;
const float MAX_10BIT = pow(2,10) - 1;
const float MAX_8BIT = pow(2, 8) - 1;
const float SCALE_FACTOR_10Bit = MAX_10BIT / MAX_8BIT;
const float SCALE_FACTOR_8Bit = MAX_8BIT / MAX_10BIT;

// Function to display and save an image
static void displayAndSaveImage(const Mat& image, const string& windowName) {
    imshow(windowName, image);

    // Construct the filename using the window name and ".png" extension
    string filename = windowName + ".png";
    imwrite(filename, image);
}

// Function to display and save an image
static void displayAndSave10BitImage(const Mat& image_10Bit, const string& windowName) {
    cv::Mat image_8Bit;
    convertScaleAbs(image_10Bit, image_8Bit, 1.0 / 4.0);

    imshow(windowName, image_8Bit);

    // Construct the filename using the window name and ".png" extension
    string filename = windowName + ".png";
    imwrite(filename, image_8Bit);
}

Mat convert10BitTo8Bit(const Mat& image_10Bit) {
    Mat image_8Bit;
    convertScaleAbs(image_10Bit, image_8Bit, 1.0 / 4.0);
    return image_8Bit;
}

Mat convert10BitTo8Bit(const Mat& image_10Bit, bool useScaling) {
    Mat image_8Bit(image_10Bit.size(), CV_8UC1);

    for (int y = 0; y < image_10Bit.rows; ++y) {
        ushort* rowPtr_10bit = (ushort*)image_10Bit.ptr<ushort>(y);
        uchar* rowPtr_8bit = image_8Bit.ptr<uchar>(y);
        for (int x = 0; x < image_10Bit.cols; ++x) {
            rowPtr_8bit[x] = useScaling ? saturate_cast<uchar>(rowPtr_10bit[x] * SCALE_FACTOR_8Bit) // Use scaling for conversion
                                        : saturate_cast<uchar>(rowPtr_10bit[x] >> BIT_SHIFT);       // Use bit-shifting for conversion
        }
    }
    return image_8Bit;
}


Mat convert8BitTo10Bit(const Mat& image_8Bit, bool useScaling) {
    Mat image_10Bit(image_8Bit.size(), CV_16UC1);
    for (int y = 0; y < image_8Bit.rows; ++y) {
        uchar* rowPtr_8bit = (uchar*)image_8Bit.ptr<uchar>(y);
        ushort* rowPtr_10bit = image_10Bit.ptr<ushort>(y);
        for (int x = 0; x < image_8Bit.cols; ++x) {
            rowPtr_10bit[x] = useScaling ? saturate_cast<ushort>(rowPtr_8bit[x] * SCALE_FACTOR_10Bit) : // Use scaling for conversion
                                           rowPtr_8bit[x] << BIT_SHIFT;                                 // Use bit-shifting for conversion
        }
    }
    return image_10Bit;
}

void calculateErrorMetrics(const Mat& original, const Mat& converted, double& rmse, double& psnr) {
    Mat diff;
    absdiff(original, converted, diff);
    diff.convertTo(diff, CV_32F);
    diff = diff.mul(diff);

    Scalar mse = mean(diff);
    rmse = sqrt(mse[0]);
    psnr = 20 * log10(MAX_10BIT) - 10 * log10(mse[0]);
}

void Test10bitTo8BitConversion(cv::Mat img_10bit) {
    // Convert to 8-bit using both methods
    Mat img_8bit_shift = convert10BitTo8Bit(img_10bit, false);
    Mat img_8bit_scale = convert10BitTo8Bit(img_10bit, true);

    // Display and save both images
    displayAndSaveImage(img_8bit_shift, "img_8bit_shift");
    displayAndSaveImage(img_8bit_scale, "img_8bit_scale");

    // Compare the two images
    double maxDiff = cv::norm(img_8bit_shift, img_8bit_scale, NORM_INF);
    if (maxDiff == 0) {
        cout << "The images are exactly the same." << endl;
    }
    else {
        cout << "The images are not the same." << endl;

        Mat img_8bit_diff;
        absdiff(img_8bit_shift, img_8bit_scale, img_8bit_diff);

        cv::threshold(img_8bit_diff, img_8bit_diff, 0, 255, THRESH_BINARY);

        displayAndSaveImage(img_8bit_diff, "img_8bit_diff");
    }
}

int main() {
    // Create a 10-bit gradient image (1024x100)
    Size ImageSize(MAX_10BIT, 300);
    Mat img_10bit(ImageSize, CV_16UC1);
    for (int y = 0; y < img_10bit.rows; ++y) {
        ushort* rowPtr = img_10bit.ptr<ushort>(y);
        for (int x = 0; x < img_10bit.cols; ++x) {
            rowPtr[x] = x;
        }
    }

    Test10bitTo8BitConversion(img_10bit);

    Mat img_8bit = convert10BitTo8Bit(img_10bit);
    Mat img_10bit_shift = convert8BitTo10Bit(img_8bit, false);
    Mat img_10bit_scale = convert8BitTo10Bit(img_8bit, true);

    displayAndSave10BitImage(img_10bit_shift, "img_10bit_shift");
    displayAndSave10BitImage(img_10bit_scale, "img_10bit_scale");

    double shift_rmse = 0.0;
    double shift_psnr = 0.0;
    double scale_rmse = 0.0;
    double scale_psnr = 0.0;

    calculateErrorMetrics(img_10bit, img_10bit_shift, shift_rmse, shift_psnr);
    calculateErrorMetrics(img_10bit, img_10bit_scale, scale_rmse, scale_psnr);

    // Print Results
    cout << "Bit Shifting:\n RMSE: " << shift_rmse << "\n PSNR: " << shift_psnr << endl;
    cout << "Scaling Factor:\n RMSE: " << scale_rmse << "\n PSNR: " << scale_psnr << endl;

    // First, determine the better method based on PSNR and RMSE separately
    string betterMethodPSNR = (shift_psnr > scale_psnr) ? "Bit Shifting" : "Scaling Factor";
    string betterMethodRMSE = (shift_rmse < scale_rmse) ? "Bit Shifting" : "Scaling Factor";

    // Then, calculate the percentage improvement for PSNR and RMSE accurately
    double psnrImprovement = 0.0;
    double rmseImprovement = 0.0;

    if (shift_psnr != scale_psnr) { // Ensure we do not divide by zero
        // For PSNR, higher is better
        psnrImprovement = (betterMethodPSNR == "Bit Shifting") ?
            (shift_psnr - scale_psnr) / scale_psnr * 100 :
            (scale_psnr - shift_psnr) / shift_psnr * 100;
    }

    if (shift_rmse != scale_rmse) { // Ensure we do not divide by zero
        // For RMSE, lower is better
        rmseImprovement = (betterMethodRMSE == "Bit Shifting") ?
            (scale_rmse - shift_rmse) / scale_rmse * 100 :
            (shift_rmse - scale_rmse) / shift_rmse * 100;
    }

    // Print the results
    cout << "Better method is: " << betterMethodPSNR << endl;
    if (psnrImprovement != 0) {
        cout << "PSNR Improvement: " << abs(psnrImprovement) << "%" << endl;
    }
    else {
        cout << "No PSNR Improvement, both methods are equal." << endl;
    }

    if (rmseImprovement != 0) {
        cout << "RMSE Improvement: " << abs(rmseImprovement) << "%" << " (in favor of the method with better RMSE)" << endl;
    }
    else {
        cout << "No RMSE Improvement, both methods are equal." << endl;
    }

    waitKey(0);
    return 0;
}

输出:

Bit Shifting:
 RMSE: 1.22494
 PSNR: 58.4352
Scaling Factor:
 RMSE: 2.16025
 PSNR: 53.5074

Better method is: Bit Shifting
PSNR Improvement: 9.20945%
RMSE Improvement: 43.2961% (in favor of the method with better RMSE)

输入渐变图像: 8 位差异图像:

预期行为:

我期望在 8 位和 10 位表示之间转换图像时,位移和缩放方法都会产生类似的结果,假设来回转换会产生图像质量指标(RMSE 和 PSNR)的微不足道的差异。

观察到的行为:

但是,我观察到使用位移方法转换的图像与使用缩放方法转换的图像有很大不同。这种差异可以通过 RMSE 和 PSNR 指标进行量化,表明转换的准确性或质量存在差异。

问题:

  1. 执行从 8 位到 8 位转换的正确方法是什么? 10 位以确保准确性并保持图像质量?
  2. 为什么两者的图像质量存在显着差异 深度转换的位移和缩放方法?

任何有关如何解决此问题的见解或建议将不胜感激。预先感谢您!

更正代码:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

// Constants for bit depth conversion
const int   BIT_SHIFT = 2;
const float MAX_10BIT = pow(2,10);
const float MAX_8BIT = pow(2, 8) ;
const float SCALE_FACTOR_10Bit = MAX_10BIT / MAX_8BIT;

// Function to display and save an image
static void displayAndSaveImage(const Mat& image, const string& windowName) {
    imshow(windowName, image);

    // Construct the filename using the window name and ".png" extension
    string filename = windowName + ".png";
    imwrite(filename, image);
}

// Function to display and save an image
static void displayAndSave10BitImage(const Mat& image_10Bit, const string& windowName) {
    cv::Mat image_8Bit;
    convertScaleAbs(image_10Bit, image_8Bit, 1.0 / 4.0);

    imshow(windowName, image_8Bit);

    // Construct the filename using the window name and ".png" extension
    string filename = windowName + ".png";
    imwrite(filename, image_8Bit);
}

Mat convert10BitTo8Bit(const Mat& image_10Bit) {
    Mat image_8Bit;
    convertScaleAbs(image_10Bit, image_8Bit, 1.0 / 4.0);
    return image_8Bit;
}

Mat convert10BitTo8Bit(const Mat& image_10Bit, bool useScaling) {
    Mat image_8Bit(image_10Bit.size(), CV_8UC1);

    for (int y = 0; y < image_10Bit.rows; ++y) {
        ushort* rowPtr_10bit = (ushort*)image_10Bit.ptr<ushort>(y);
        uchar* rowPtr_8bit = image_8Bit.ptr<uchar>(y);
        for (int x = 0; x < image_10Bit.cols; ++x) {
            rowPtr_8bit[x] = useScaling ? saturate_cast<uchar>(rowPtr_10bit[x] / 4 )                 // Use scaling for conversion
                                        : saturate_cast<uchar>(rowPtr_10bit[x] >> BIT_SHIFT);       // Use bit-shifting for conversion
        }
    }
    return image_8Bit;
}


Mat convert8BitTo10Bit(const Mat& image_8Bit, bool useScaling) {
    Mat image_10Bit(image_8Bit.size(), CV_16UC1);
    for (int y = 0; y < image_8Bit.rows; ++y) {
        uchar* rowPtr_8bit = (uchar*)image_8Bit.ptr<uchar>(y);
        ushort* rowPtr_10bit = image_10Bit.ptr<ushort>(y);
        for (int x = 0; x < image_8Bit.cols; ++x) {
            rowPtr_10bit[x] = useScaling ? saturate_cast<ushort>(rowPtr_8bit[x] * SCALE_FACTOR_10Bit) : // Use scaling for conversion
                                           rowPtr_8bit[x] << BIT_SHIFT;                                 // Use bit-shifting for conversion
        }
    }
    return image_10Bit;
}

void calculateErrorMetrics(const Mat& original, const Mat& converted, double& rmse, double& psnr) {
    Mat diff;
    absdiff(original, converted, diff);
    diff.convertTo(diff, CV_32F);
    diff = diff.mul(diff);

    Scalar mse = mean(diff);
    rmse = sqrt(mse[0]);
    psnr = 20 * log10(MAX_10BIT-1) - 10 * log10(mse[0]);
}

void Test10bitTo8BitConversion(cv::Mat img_10bit) {
    // Convert to 8-bit using both methods
    Mat img_8bit_shift = convert10BitTo8Bit(img_10bit, false);
    Mat img_8bit_scale = convert10BitTo8Bit(img_10bit, true);

    // Display and save both images
    displayAndSaveImage(img_8bit_shift, "img_8bit_shift");
    displayAndSaveImage(img_8bit_scale, "img_8bit_scale");

    // Compare the two images
    double maxDiff = cv::norm(img_8bit_shift, img_8bit_scale, NORM_INF);
    if (maxDiff == 0) {
        cout << "The images are exactly the same." << endl;
    }
    else {
        cout << "The images are not the same." << endl;

        Mat img_8bit_diff;
        absdiff(img_8bit_shift, img_8bit_scale, img_8bit_diff);

        cv::threshold(img_8bit_diff, img_8bit_diff, 0, 255, THRESH_BINARY);

        displayAndSaveImage(img_8bit_diff, "img_8bit_diff");
    }
}

int main() {
    // Create a 10-bit gradient image (1024x100)
    Size ImageSize(MAX_10BIT, 300);
    Mat img_10bit(ImageSize, CV_16UC1);
    for (int y = 0; y < img_10bit.rows; ++y) {
        ushort* rowPtr = img_10bit.ptr<ushort>(y);
        for (int x = 0; x < img_10bit.cols; ++x) {
            rowPtr[x] = x;
        }
    }

    Test10bitTo8BitConversion(img_10bit);

    Mat img_8bit = convert10BitTo8Bit(img_10bit);
    Mat img_10bit_shift = convert8BitTo10Bit(img_8bit, false);
    Mat img_10bit_scale = convert8BitTo10Bit(img_8bit, true);

    displayAndSave10BitImage(img_10bit_shift, "img_10bit_shift");
    displayAndSave10BitImage(img_10bit_scale, "img_10bit_scale");

    double shift_rmse = 0.0;
    double shift_psnr = 0.0;
    double scale_rmse = 0.0;
    double scale_psnr = 0.0;

    calculateErrorMetrics(img_10bit, img_10bit_shift, shift_rmse, shift_psnr);
    calculateErrorMetrics(img_10bit, img_10bit_scale, scale_rmse, scale_psnr);

    Mat img_10bit_diff;
    absdiff(img_10bit_shift, img_10bit_scale, img_10bit_diff);

    cv::threshold(img_10bit_diff, img_10bit_diff, 0, 255, THRESH_BINARY);

    displayAndSaveImage(img_10bit_diff, "img_10bit_diff");

    // Print Results
    cout << "Bit Shifting:\n RMSE: " << shift_rmse << "\n PSNR: " << shift_psnr << endl;
    cout << "Scaling Factor:\n RMSE: " << scale_rmse << "\n PSNR: " << scale_psnr << endl;

    // First, determine the better method based on PSNR and RMSE separately
    string betterMethodPSNR = (shift_psnr > scale_psnr) ? "Bit Shifting" : "Scaling Factor";
    string betterMethodRMSE = (shift_rmse < scale_rmse) ? "Bit Shifting" : "Scaling Factor";

    // Then, calculate the percentage improvement for PSNR and RMSE accurately
    double psnrImprovement = 0.0;
    double rmseImprovement = 0.0;

    if (shift_psnr != scale_psnr) { // Ensure we do not divide by zero
        // For PSNR, higher is better
        psnrImprovement = (betterMethodPSNR == "Bit Shifting") ?
            (shift_psnr - scale_psnr) / scale_psnr * 100 :
            (scale_psnr - shift_psnr) / shift_psnr * 100;
    }

    if (shift_rmse != scale_rmse) { // Ensure we do not divide by zero
        // For RMSE, lower is better
        rmseImprovement = (betterMethodRMSE == "Bit Shifting") ?
            (scale_rmse - shift_rmse) / scale_rmse * 100 :
            (shift_rmse - scale_rmse) / shift_rmse * 100;
    }

    // Print the results
    if (psnrImprovement != 0) {
        cout << "Better method is: " << betterMethodPSNR << endl;
    }

    if (psnrImprovement != 0) {
        cout << "PSNR Improvement: " << abs(psnrImprovement) << "%" << endl;
    }
    else {
        cout << "No PSNR Improvement, both methods are equal." << endl;
    }

    if (rmseImprovement != 0) {
        cout << "RMSE Improvement: " << abs(rmseImprovement) << "%" << " (in favor of the method with better RMSE)" << endl;
    }
    else {
        cout << "No RMSE Improvement, both methods are equal." << endl;
    }

    waitKey(0);
    return 0;
}
c++ opencv
1个回答
0
投票

由于您没有充分指定“图像质量”,因此您的问题没有唯一的答案。位移 (*4) 和缩放 (*1023/255) 都是从 8 位到 10 位的无损转换,因此对于大多数图像质量定义来说,没有任何损失。

如果您的伽马校正曲线已知,但您没有告诉我们,情况可能会略有不同。此外,对于许多输入域,可以利用相邻像素之间的空间相关性。您的缩放纯粹是局部的,不会考虑相邻像素。

© www.soinside.com 2019 - 2024. All rights reserved.