Реализация нерезкой маски с OpenCV

avatar
google dev
8 августа 2021 в 18:01
1000
2
1

Я хочу применить нерезкую маску, как в Adobe Photoshop, Я знаю этот ответ, но он не такой резкий, как Photoshop.

Photoshop имеет 3 параметра в диалоговом окне Smart Sharpen: Amount, Radius, Reduce Noise; Я хочу реализовать их все. enter image description here

Это код, который я написал, согласно различным источникам в 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 https://coderhelper.com/a/596243/7206675
float luminanceAsPercent(Vec3b color)
{
    return (0.2126 * color[2]) + (0.7152 * color[1]) + (0.0722 * color[0]);
}

// from https://coderhelper.com/a/2938365/7206675
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);
}

Исходное изображение: enter image description here

Размытая сцена — на первый взгляд хорошо: enter image description here

Стадия UnsharpMask — на первый взгляд хорошо: enter image description here

Этап HighContrast — на вид хороший: enter image description here

Этап результата моего кода - выглядит плохо! enter image description here

Результат из Photoshop - Отлично! enter image description here

Источник
Christoph Rackwitz
8 августа 2021 в 21:05
0

объяснить "последний этап результат не является хорошим".

Mark Setchell
8 августа 2021 в 21:58
3

Возможно, стоит подумать о переходе на float, прежде чем делать всю эту математику, чтобы избежать округления, переполнения и допустить отрицательные значения.

google dev
9 августа 2021 в 08:58
0

@ChristophRackwitz Я объяснил и прикрепил примеры фотографий для каждого шага.

Ответы (2)

avatar
vvanpelt
11 августа 2021 в 20:25
6

Во-первых, судя по артефактам, которые Photoshop оставил на границах лепестков, я бы сказал, что он применяет маску, используя взвешенную сумму между исходным изображением и маской, как в ответе вы пытались первым.

Я модифицировал ваш код, чтобы реализовать эту схему, и попытался настроить параметры так, чтобы они были как можно ближе к результату Photoshop, но у меня не получилось, не создав много шума. Я не стал бы пытаться угадать, что именно делает Photoshop (мне определенно хотелось бы знать), однако я обнаружил, что это довольно воспроизводимо, применяя фильтр к маске для уменьшения шума. Схема алгоритма будет такой:

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

Я реализовал это и попытался использовать базовые фильтры (среднее размытие, средний фильтр и т. д.) на маске, и вот какой результат я могу получить: comparison plot

что немного шумнее, чем изображение в Photoshop, но, на мой взгляд, достаточно близко к тому, что вы хотели.

С другой стороны, это, конечно, будет зависеть от того, как вы используете свой фильтр, но я думаю, что настройки, которые вы использовали в Photoshop, слишком сильны (у вас есть большие выбросы возле границ лепестков). Этого достаточно, чтобы невооруженным глазом получить хорошее изображение с ограниченным выбросом: comparison plot

Наконец, вот код, который я использовал для создания двух изображений выше:

#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); 
}
Softlion
1 марта 2022 в 10:23
0

Каким будет хороший порог?

vvanpelt
2 марта 2022 в 10:15
0

@Softlion Порог здесь в основном полезен для отбрасывания ненулевых пикселей маски, возникающих из-за шума изображения, а не из-за реального края. Поэтому я предполагаю, что значение, которое вы выберете, будет выше, чем амплитуда шума, но ниже, чем амплитуда краев, которые вы хотите сделать более резкими.

avatar
dhanushka
15 августа 2021 в 16:51
0

Вот моя попытка "умного" нерезкого маскирования. Результат не очень, но все равно пишу. В Википедии статья о нерезком маскировании содержит подробную информацию об интеллектуальной резкости.

Некоторые вещи я сделал иначе:

  • Преобразование BGR в цветовое пространство Lab и применение улучшений к каналу яркости
  • Использование карты краев для улучшения краевых областей

Оригинал:

orig

Расширенный: сигма=2 количество=3 низкий=0,3 высокий=0,8 w=2

enh

Карта границ: низкая=0,3 высокая=0,8 w=2

edges

#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;
}