cv2.fastNlMeansDenoising()的怪异行为

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

也根据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

python opencv parameters
1个回答
3
投票
让我们首先看一下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绑定中,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是为了进行比较,所以我们也进行检查。

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 参数在序列的后面出现,因此您可能会犯一个唯一的错误-改为输入边框类型。

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

总结:这些函数的行为始终如一,但是在dst之后的可选参数越少,击倒自己的机会就越少。

Python绑定如何工作


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

让我们看一下包装dst的代码部分:

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; } 用于解析函数自变量并将其值分配给相应的C ++变量(或保留默认值(如果可选且缺失,则保留默认值)。)>

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

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

pyopencv_to的转换如下:

如果参数为cv::Mat,请留空cv::add

    如果参数是整数(标量),则创建一个具有4行1列且数据类型为64位浮点值的cv::add。将第一行设置为提供的整数的值,其余设置为0。
  • 如果参数是浮点数(标量),则与整数(以上)相同。
  • 如果参数是一个数字元组,则创建一个具有cv::Mat行和1列且数据类型为64位浮点值的None,其中Mat是该元组中的元素数。每行依次包含一个元素。
  • 最后,处理数组(超出此答案的范围。)>
  • 这意味着当您调用Mat时,整数Mat成为具有64位浮点元素的4x1单通道n。因此,可以毫无问题地调用基础C ++函数。现在,为什么不抱怨n的大小和数据类型存储输出错误?
  • cv2.fastNlMeansDenoising(gray, 31, 7, 21)的工作方式


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

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

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

这很容易证明:

Input/OutputArray

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