gpt4 book ai didi

python - cv2.fastNlMeansDenoising()的怪异行为

转载 作者:行者123 更新时间:2023-11-30 21:54:05 25 4
gpt4 key购买 nike

根据this documentation of opencvthis linkthis link:
C++:

void fastNlMeansDenoising(InputArray src, OutputArray dst, float h=3, int templateWindowSize=7, int searchWindowSize=21 )  
python :
cv2.fastNlMeansDenoising(src[, dst[, h[, templateWindowSize[, searchWindowSize]]]]) → dst
参数如下( ):
  • src –输入图像。
  • dst –输出与src具有相同大小和类型的图像。
  • templateWindowSize –模板补丁的大小(以像素为单位)。应该是奇怪的。
  • searchWindowSize –以窗口像素为单位的大小。应该很奇怪。
  • h –调节过滤强度的参数。

  • 据我所知,在Python中,我们可以将dst / output变量从方法中删除为 dst = cv2.method(input, param1, param2, ..., paramx)。而且我们不需要在方法内部放置任何内容(即,我们不需要这样做: dst = cv2.method(input, None, param1, param2, ..., paramx)
    尽管此方法适用于不同的OpenCV方法,但不适用于 fastNlMeansDenoising
    以下代码将阐明我的问题:
    import cv2
    import numpy as np


    def thresh(filename):
    img = cv2.imread(filename)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    #without adding None instead of dst
    test_1 = cv2.fastNlMeansDenoising(gray, 31, 7, 21)
    cv2.imwrite('test_1.jpg', test_1)

    # Adding None instead of dst
    test_2 = cv2.fastNlMeansDenoising(gray, None, 31, 7, 21)
    cv2.imwrite('test_2.jpg', test_2)

    # putting dst inside the method
    test_3 = np.empty(gray.shape, np.uint8)
    cv2.fastNlMeansDenoising(gray, test_3, 31, 7, 21)
    cv2.imwrite('test_3.jpg', test_3)

    # Adding the input params
    test_4 = cv2.fastNlMeansDenoising(gray, h=31, templateWindowSize=7,
    searchWindowSize=21)
    cv2.imwrite('test_4.jpg', test_4)

    blur = cv2.bilateralFilter(gray, 31, 7, 21)
    cv2.imwrite('blur.jpg', blur)
    blur_ = cv2.bilateralFilter(gray, 31, 7, 21, None)
    cv2.imwrite('blur_.jpg', blur_)
    blur__ = np.empty(gray.shape, np.uint8)
    cv2.bilateralFilter(gray, 31, 7, 21, blur__)
    cv2.imwrite('blur__.jpg', blur__)


    thresh('test.png')
    这是输入图像:
    enter image description here
    如您所见,如果您运行代码,则test_2.jpg,test_3.jpg和test_4.jpg相似。并且test_1.jpg与 gray相同(好像 test_1没有收到 fastNlMeansDenoising的输出)。
    但是, bilateralFilter并非如此:blur.jpg,blur_.jpg和blur __。jpg都是相同的,尽管我重复了与 fastNlMeansDenoising相同的过程。
    有什么解释吗?为什么要在 None参数中添加 fastNlMeansDenoising

    最佳答案

    函数fastNlMeansDenoising
    首先让我们看一下the Python function的签名:

    cv2.fastNlMeansDenoising(src[, dst[, h[, templateWindowSize[, searchWindowSize]]]]) → dst

    方括号( [])嵌套的方式意味着第2-5个参数是可选的,但是只要将它们作为位置参数传递进来,序列就必须保持相同(即,您不能跳过任何一个)。

    这意味着仅使用位置参数,有5种可能性:
    cv2.fastNlMeansDenoising(src) → dst
    cv2.fastNlMeansDenoising(src, dst) → dst
    cv2.fastNlMeansDenoising(src, dst, h) → dst
    cv2.fastNlMeansDenoising(src, dst, h, templateWindowSize) → dst
    cv2.fastNlMeansDenoising(src, dst, h, templateWindowSize, searchWindowSize) → dst

    未提供的任何可选参数将使用默认值。可以从相应的C++函数签名中推导出所使用的默认值。
    void fastNlMeansDenoising(InputArray src, OutputArray dst, float h=3, int templateWindowSize=7, int searchWindowSize=21)

    对于最后三个参数,这很明显- h=3templateWindowSize=7searchWindowSize=21。在Python绑定(bind)中, OutputArray参数隐式具有 None(与C++ API不同,Python变体也返回输出)。

    考虑到这一点,您的第一个变体
    test_1 = cv2.fastNlMeansDenoising(gray, 31, 7, 21)

    手段
    test_1 = cv2.fastNlMeansDenoising(src=gray, dst=31, h=7, templateWindowSize=21, searchWindowSize=21)

    h比您预期的要小得多,而 templateWindowSize则要大得多。这就是为什么结果不同的原因。

    我们将探讨为什么将 dst设置为31不会导致任何明显的错误在稍后的答案中引发。

    第四个变体是恕我直言,这是跳过 dst的最佳方法:
    test_4 = cv2.fastNlMeansDenoising(gray, h=31, templateWindowSize=7, searchWindowSize=21)

    明确使用关键字参数时,您不太可能混淆。

    第二个变体(将 None作为第二个参数传递)是可以的。

    第三种变体在循环中很有用,它允许您在后续迭代中重用临时数组,并避免重新分配(这可能会导致成本高昂)。但是,有一个陷阱-数组必须具有所需的形状和数据类型。如果没有,它将不会被修改(但是该函数仍会返回一个新分配的数组,其中包含要捕获的结果),您需要捕获该数组。

    继续阅读的原因很明显。

    函数 bilateralFilter
    您提到 bilateralFilter 是为了进行比较,因此我们也对其进行检查。
    cv.bilateralFilter(src, d, sigmaColor, sigmaSpace[, dst[, borderType]]) → dst

    这意味着只有三种使用位置参数调用此方法的可能性:
    cv.bilateralFilter(src, d, sigmaColor, sigmaSpace) → dst
    cv.bilateralFilter(src, d, sigmaColor, sigmaSpace, dst) → dst
    cv.bilateralFilter(src, d, sigmaColor, sigmaSpace, dst, borderType) → dst

    请注意,由于 dst参数出现在序列的后面,因此您可能会犯一个错误,那就是传入border类型。

    在您的代码示例中,您仅使用了4个或5个参数,甚至从未使用过 borderType,并且在所有情况下 dst都具有有意义的值。

    综上所述:函数的行为始终如一,但是 dst后面的可选参数越少,击掌的机会就越少。

    Python绑定(bind)如何工作

    由于需要向Python公开OpenCV代码库的大小,因此会自动生成C++函数的包装器。由于API的复杂性,除非您详细研究实现,否则某些行为可能不会立即显而易见。 (由于实际的绑定(bind)代码是在构建时自动生成的,因此最好在本地编译OpenCV以检查生成的实现。)

    让我们看一下包装 fastNlMeansDenoising的代码部分:
    static PyObject* pyopencv_cv_fastNlMeansDenoising(PyObject* , PyObject* args, PyObject* kw)
    {
    using namespace cv;

    {
    PyObject* pyobj_src = NULL;
    Mat src;
    PyObject* pyobj_dst = NULL;
    Mat dst;
    float h=3;
    int templateWindowSize=7;
    int searchWindowSize=21;

    const char* keywords[] = { "src", "dst", "h", "templateWindowSize", "searchWindowSize", NULL };
    if( PyArg_ParseTupleAndKeywords(args, kw, "O|Ofii:fastNlMeansDenoising", (char**)keywords, &pyobj_src, &pyobj_dst, &h, &templateWindowSize, &searchWindowSize) &&
    pyopencv_to(pyobj_src, src, ArgInfo("src", 0)) &&
    pyopencv_to(pyobj_dst, dst, ArgInfo("dst", 1)) )
    {
    ERRWRAP2(cv::fastNlMeansDenoising(src, dst, h, templateWindowSize, searchWindowSize));
    return pyopencv_from(dst);
    }
    }

    // Clear Python error, try the same for UMat

    // Clear Python error, try overload with Mat

    // Clear Python error, try overload with UMat

    return NULL;
    }

    首先, PyArg_ParseTupleAndKeywords 用于解析函数参数,并将其值分配给相应的C++变量(如果可选且缺少,则保留预设的默认值)。

    重要的是要注意,当相应的C++参数的类型为 Input/OutputArray时,它将被解析为Python对象(格式字符串中的 O)-这意味着在此阶段它可以是任何东西。

    解析参数后, pyopencv_to 用于将Python对象转换为 cv::Mat。由于许多OpenCV函数(例如 cv::add )都允许某些输入参数(以及潜在的输出参数)既是数组又是标量,因此Python绑定(bind)也支持此功能。

    转换为 cv::Mat的工作方式如下:
  • 如果参数为None,则保留一个空的Mat
  • 如果参数是整数(标量),则创建一个Mat,该行具有4行1列,数据类型为64位浮点值。将第一行设置为提供的整数的值,其余设置为0。
  • 如果参数是浮点数(标量),请执行与整数(上方)相同的操作。
  • 如果参数是一个数字元组,则创建一个Mat,该行具有n行和1列,数据类型为64位浮点值,其中n是元组中的元素数。每行依次包含一个元素。
  • 最后,处理数组(超出此答案的范围)。

  • 这意味着,当您调用 cv2.fastNlMeansDenoising(gray, 31, 7, 21)时,整数 31成为具有64位浮点元素的4x1单通道 Mat。因此,可以毫无问题地调用基础C++函数。现在,为什么不抱怨 Mat的大小和数据类型存储输出错误?
    OutputArray的工作方式

    由于C++ API使用支持返回值的输出数组参数,因此它需要能够支持在调用函数之前无法确定结果大小的情况。为了解决该问题,在给定空 Mat或形状或数据类型不正确的 Mat的情况下,将重新创建 Mat(分配新的缓冲区等)以满足要求。由于 Mat本质上是指向底层图像缓冲区的智能指针,因此可以正常工作,并且在C++中非常可预测(IMHO)-即使发生重新分配,您作为输出参数提供的 Mat实例也将正确地引用新数据。

    这就解释了为什么 31可以作为 dst正常的原因-它产生了错误的形状和类型的 Mat,但是只是重新分配了,一切都很好。

    但是,这个不错的功能在Python API中引入了一些障碍。当为 Input/OutputArray参数提供numpy数组时,将创建一个 Mat实例,该实例共享用于保存值的基础缓冲区。这意味着操作很快(因为没有数据被复制),并且numpy数组自动反射(reflect)了对 Mat所做的更改。但是,如果OpenCV由于形状/类型不正确而重新分配了 Mat,则会分配一个新缓冲区,并且原始numpy数组将保持不变。

    这很容易证明:
    >>> a = np.ones((3,3), np.uint8)
    >>> b = a + 1
    >>> c = np.zeros(a.shape, np.float32)
    >>> c
    array([[ 0., 0., 0.],
    [ 0., 0., 0.],
    [ 0., 0., 0.]], dtype=float32)
    >>> cv2.add(a, b, c)
    array([[3, 3, 3],
    [3, 3, 3],
    [3, 3, 3]], dtype=uint8)
    >>> c
    array([[ 0., 0., 0.],
    [ 0., 0., 0.],
    [ 0., 0., 0.]], dtype=float32)
    >>> d = np.zeros_like(a)
    >>> cv2.add(a, b, d)
    array([[3, 3, 3],
    [3, 3, 3],
    [3, 3, 3]], dtype=uint8)
    >>> d
    array([[3, 3, 3],
    [3, 3, 3],
    [3, 3, 3]], dtype=uint8)

    关于python - cv2.fastNlMeansDenoising()的怪异行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59424253/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com