使用 OpenCV 实施 USM 锐化

Unsharp mask implementation with OpenCV

我想像 Adob​​e Photoshop 一样应用锐化蒙版, 我知道 this 答案,但不如 Photoshop 清晰。

Photoshop 在智能锐化对话框中有 3 个参数:数量、半径、减少噪点;我想实现所有这些。

这是我根据 SO 中的各种来源编写的代码。 但是在某些阶段("blurred"、"unsharpMask"、"highContrast")结果很好,但在最后阶段("retval")结果不好。

我哪里错了,我应该改进什么?

是否可以在性能方面改进以下算法?

#include "opencv2/opencv.hpp"
#include "fstream"
#include "iostream"
#include <chrono>

using namespace std;
using namespace cv;

// from https://docs.opencv.org/3.4/d3/dc1/tutorial_basic_linear_transform.html
void increaseContrast(Mat img, Mat* dst, int amountPercent)
{
    *dst = img.clone();
    double alpha = amountPercent / 100.0;
    *dst *= alpha;
}

// from 
float luminanceAsPercent(Vec3b color)
{
    return (0.2126 * color[2]) + (0.7152 * color[1]) + (0.0722 * color[0]);
}

// from 
Mat usm(Mat original, int radius, int amountPercent, int threshold)
{
    // copy original for our return value
    Mat retval = original.clone();

    // create the blurred copy
    Mat blurred;
    cv::GaussianBlur(original, blurred, cv::Size(0, 0), radius);

    cv::imshow("blurred", blurred);
    waitKey();

    // subtract blurred from original, pixel-by-pixel to make unsharp mask
    Mat unsharpMask;
    cv::subtract(original, blurred, unsharpMask);

    cv::imshow("unsharpMask", unsharpMask);
    waitKey();

    Mat highContrast;
    increaseContrast(original, &highContrast, amountPercent);

    cv::imshow("highContrast", highContrast);
    waitKey();

    // assuming row-major ordering
    for (int row = 0; row < original.rows; row++) 
    {
        for (int col = 0; col < original.cols; col++) 
        {
            Vec3b origColor = original.at<Vec3b>(row, col);
            Vec3b contrastColor = highContrast.at<Vec3b>(row, col);

            Vec3b difference = contrastColor - origColor;
            float percent = luminanceAsPercent(unsharpMask.at<Vec3b>(row, col));

            Vec3b delta = difference * percent;

            if (*(uchar*)&delta > threshold) {
                retval.at<Vec3b>(row, col) += delta;
                //retval.at<Vec3b>(row, col) = contrastColor;
            }
        }
    }

    return retval;
}

int main(int argc, char* argv[])
{
    if (argc < 2) exit(1);
    Mat mat = imread(argv[1]);
    mat = usm(mat, 4, 110, 66);
    imshow("usm", mat);
    waitKey();
    //imwrite("USM.png", mat);
}

原图:

模糊阶段 - 看似不错:

UnsharpMask 阶段 - 看起来不错:

HighContrast 阶段 - 看起来不错:

我的代码的结果阶段 - 看起来很糟糕!

来自 Photoshop 的结果 - 太棒了!

首先,从 Photoshop 在花瓣边缘留下的人工痕迹来看,我会说它通过使用原始图像和蒙版之间的加权和来应用蒙版,如 answer you tried first.

我修改了您的代码以实现此方案,并尝试调整参数以尽可能接近 Photoshop 结果,但我无法避免产生很多噪音。我不会尝试猜测 Photoshop 到底在做什么(我绝对想知道),但我发现通过在蒙版上应用一些滤镜来减少噪音,它是相当可重现的。算法方案为:

blurred = blur(image, Radius)
mask = image - blurred
mask = some_filter(mask)
sharpened = (mask < Threshold) ? image : image - Amount * mask

我实现了这个并尝试在蒙版上使用基本过滤器(中值模糊、均值过滤器等),这是我可以获得的结果:

这比 Photoshop 图像有点嘈杂,但在我看来,已经足够接近您想要的效果了。

另一方面,这当然取决于您对滤镜的使用情况,但我认为您在 Photoshop 中使用的设置太强了(花瓣边界附近有很大的过冲)。这足以在肉眼中获得漂亮的图像,并且超调量有限:

最后,这是我用来生成上面两张图片的代码:

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

using namespace std;
using namespace cv;

Mat usm(Mat original, float radius, float amount, float threshold)
{
    // work using floating point images to avoid overflows
    cv::Mat input;
    original.convertTo(input, CV_32FC3);

    // copy original for our return value
    Mat retbuf = input.clone();

    // create the blurred copy
    Mat blurred;
    cv::GaussianBlur(input, blurred, cv::Size(0, 0), radius);

    // subtract blurred from original, pixel-by-pixel to make unsharp mask
    Mat unsharpMask;
    cv::subtract(input, blurred, unsharpMask);
    
    // --- filter on the mask ---
    
    //cv::medianBlur(unsharpMask, unsharpMask, 3);
    cv::blur(unsharpMask, unsharpMask, {3,3});
    
    // --- end filter ---

    // apply mask to image
    for (int row = 0; row < original.rows; row++) 
    {
        for (int col = 0; col < original.cols; col++) 
        {
            Vec3f origColor = input.at<Vec3f>(row, col);
            Vec3f difference = unsharpMask.at<Vec3f>(row, col);

            if(cv::norm(difference) >= threshold) {
                retbuf.at<Vec3f>(row, col) = origColor + amount * difference;
            }
        }
    }

    // convert back to unsigned char
    cv::Mat ret;
    retbuf.convertTo(ret, CV_8UC3);

    return ret;
}

int main(int argc, char* argv[])
{
    if (argc < 3) exit(1);
    Mat original = imread(argv[1]);
    Mat expected = imread(argv[2]);
    
    // closer to Photoshop
    Mat current = usm(original, 0.8, 12., 1.);
    
    // better settings (in my opinion)
    //Mat current = usm(original, 2., 1., 3.);
    
    cv::imwrite("current.png", current);
    
    // comparison plot
    cv::Rect crop(127, 505, 163, 120);
    cv::Mat crops[3];
    cv::resize(original(crop), crops[0], {0,0}, 4, 4, cv::INTER_NEAREST);
    cv::resize(expected(crop), crops[1], {0,0}, 4, 4, cv::INTER_NEAREST);
    cv::resize( current(crop), crops[2], {0,0}, 4, 4, cv::INTER_NEAREST);
    
    char const* texts[] = {"original", "photoshop", "current"};
    
    cv::Mat plot = cv::Mat::zeros(120 * 4, 163 * 4 * 3, CV_8UC3);
    for(int i = 0; i < 3; ++i) {
        cv::Rect region(163 * 4 * i, 0, 163 * 4, 120 * 4);
        crops[i].copyTo(plot(region));
        cv::putText(plot, texts[i], region.tl() + cv::Point{5,40}, 
            cv::FONT_HERSHEY_SIMPLEX, 1.5, CV_RGB(255, 0, 0), 2.0);
    }
    
    cv::imwrite("plot.png", plot); 
}

这是我对 'smart' 锐化蒙版的尝试。结果不是很好,但我还是发帖了。维基百科 article 关于反锐化蒙版有关于智能锐化的详细信息。

我做了一些不同的事情:

  • 将 BGR 转换为 Lab 颜色space并将增强应用到亮度通道
  • 使用边缘贴图对边缘区域应用增强

原文:

增强:sigma=2 amount=3 low=0.3 high=.8 w=2

边缘图:low=0.3 high=.8 w=2

#include "opencv2/core.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <cstring>

cv::Mat not_so_smart_sharpen(
        const cv::Mat& bgr,
        double sigma,
        double amount,
        double canny_low_threshold_weight,
        double canny_high_threshold_weight,
        int edge_weight)
{
    cv::Mat enhanced_bgr, lab, enhanced_lab, channel[3], blurred, difference, bw, kernel, edges;

    // convert to Lab
    cv::cvtColor(bgr, lab, cv::ColorConversionCodes::COLOR_BGR2Lab);
    // perform the enhancement on the brightness component
    cv::split(lab, channel);
    cv::Mat& brightness = channel[0];
    // smoothing for unsharp masking
    cv::GaussianBlur(brightness, blurred, cv::Size(0, 0), sigma);
    difference = brightness - blurred;
    // calculate an edge map. I'll use Otsu threshold as the basis
    double thresh = cv::threshold(brightness, bw, 0, 255, cv::ThresholdTypes::THRESH_BINARY | cv::ThresholdTypes::THRESH_OTSU);
    cv::Canny(brightness, edges, thresh * canny_low_threshold_weight, thresh * canny_high_threshold_weight);
    // control edge thickness. use edge_weight=0 to use Canny edges unaltered
    cv::dilate(edges, edges, kernel, cv::Point(-1, -1), edge_weight);
    // unsharp masking on the edges
    cv::add(brightness, difference * amount, brightness, edges);
    // use the enhanced brightness channel
    cv::merge(channel, 3, enhanced_lab);
    // convert to BGR
    cv::cvtColor(enhanced_lab, enhanced_bgr, cv::ColorConversionCodes::COLOR_Lab2BGR);

//  cv::imshow("edges", edges);
//  cv::imshow("difference", difference * amount);
//  cv::imshow("original", bgr);
//  cv::imshow("enhanced", enhanced_bgr);
//  cv::waitKey(0);

    return enhanced_bgr;
}

int main(int argc, char *argv[])
{
    double sigma = std::stod(argv[1]);
    double amount = std::stod(argv[2]);
    double low = std::stod(argv[3]);
    double high = std::stod(argv[4]);
    int w = std::stoi(argv[5]);

    cv::Mat bgr = cv::imread("flower.jpg");

    cv::Mat enhanced = not_so_smart_sharpen(bgr, sigma, amount, low, high, w);

    cv::imshow("original", bgr);
    cv::imshow("enhanced", enhanced);
    cv::waitKey(0);

    return 0;
}