也根据this documentation of opencv,this link和this 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')
这里是输入图像:
您会注意到,如果您运行代码,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
?
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=3
,templateWindowSize=7
和searchWindowSize=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
。
cv::add
。将第一行设置为提供的整数的值,其余设置为0。cv::Mat
行和1列且数据类型为64位浮点值的None
,其中Mat
是该元组中的元素数。每行依次包含一个元素。Mat
时,整数Mat
成为具有64位浮点元素的4x1单通道n
。因此,可以毫无问题地调用基础C ++函数。现在,为什么不抱怨n
的大小和数据类型存储输出错误?cv2.fastNlMeansDenoising(gray, 31, 7, 21)
的工作方式
31
或形状或数据类型不正确的Mat
的情况下,将重新创建Mat
(分配新的缓冲区等)以满足要求。 。由于OutputArray
基本上是指向基础图像缓冲区的智能指针,因此可以正常工作,并且在C ++中非常可预测(IMHO)-即使发生重新分配,您作为输出参数提供的Mat
实例也将正确地引用新数据。Mat
作为Mat
可以-它产生了错误的形状和类型的Mat
,但是只是重新分配了,一切都很好。但是,这个不错的功能在Python API中引入了一些障碍。当为Mat
参数提供numpy数组时,将创建31
实例,该实例共享用于保存值的基础缓冲区。这意味着操作很快(因为没有数据被复制),并且numpy数组自动反映了对dst
所做的更改。但是,如果OpenCV由于不正确的形状/类型而重新分配了Mat
,则会分配一个新的缓冲区,并且原始的numpy数组保持不变。
这很容易证明:
Input/OutputArray