在撰写此答案时(OpenCV 3.4.1 是最后发布的版本),没有办法只过滤默认错误处理程序(我能想到的)的输出,也没有办法更改错误处理程序。
然而,你的问题让我思考——在highgui模块中,我们已经有几个函数可以让我们为鼠标、轨迹栏和按钮事件设置 Python 回调,因此我们可以从该代码中获取灵感并修补这个新功能。
让我们使用 3.4.1 版本。感兴趣的文件是modules/python/src2/cv2.cpp https://github.com/opencv/opencv/blob/3.4.1/modules/python/src2/cv2.cpp。我们将从函数中获取灵感开始OnMouse https://github.com/opencv/opencv/blob/3.4.1/modules/python/src2/cv2.cpp#L1538 and pycvSetMouseCallback https://github.com/opencv/opencv/blob/3.4.1/modules/python/src2/cv2.cpp#L1556.
让我们让 Python 错误处理程序有一个与C++ 错误处理程序 https://github.com/opencv/opencv/blob/3.4.1/modules/core/include/opencv2/core/utility.hpp#L168:
error_handler([int]status, [str]func_name, [str]err_msg, [str]file_name, [int]line, [obj]userdata) -> None
让我们还添加对重置为默认错误处理程序的支持——一些highgui
功能还没有做到。
首先,我们需要一个静态错误处理函数,它将参数从 C++ 编组到 Python,并调用适当的 Python 函数来处理错误。就像我们从中获得灵感的函数一样,我们将使用用户数据参数来保存一个由函数对象和可选的 Python 用户数据组成的元组。
static int OnError(int status, const char *func_name, const char *err_msg, const char *file_name, int line, void *userdata)
{
PyGILState_STATE gstate;
gstate = PyGILState_Ensure();
PyObject *o = (PyObject*)userdata;
PyObject *args = Py_BuildValue("isssiO", status, func_name, err_msg, file_name, line, PyTuple_GetItem(o, 1));
PyObject *r = PyObject_Call(PyTuple_GetItem(o, 0), args, NULL);
if (r == NULL) {
PyErr_Print();
} else {
Py_DECREF(r);
}
Py_DECREF(args);
PyGILState_Release(gstate);
return 0; // The return value isn't used
}
接下来,我们需要编写函数来实现Python和C++之间的绑定。然而,由于我的怀疑潜在的内存泄漏 https://github.com/opencv/opencv/issues/11206在我们从中汲取灵感的函数中,我们将进行一些补充来解决这个问题——我们将跟踪最新的用户数据对象并根据需要取消引用它。
// Keep track of the previous handler parameter, so we can decref it when no longer used
static PyObject* last_on_error_param = NULL;
static PyObject *pycvRedirectError(PyObject*, PyObject *args, PyObject *kw)
{
const char *keywords[] = { "on_error", "param", NULL };
PyObject *on_error;
PyObject *param = NULL;
if (!PyArg_ParseTupleAndKeywords(args, kw, "O|O", (char**)keywords, &on_error, ¶m))
return NULL;
if ((on_error != Py_None) && !PyCallable_Check(on_error)) {
PyErr_SetString(PyExc_TypeError, "on_error must be callable");
return NULL;
}
if (param == NULL) {
param = Py_None;
}
if (last_on_error_param) {
Py_DECREF(last_on_error_param);
last_on_error_param = NULL;
}
if (on_error == Py_None) {
ERRWRAP2(redirectError(NULL));
} else {
last_on_error_param = Py_BuildValue("OO", on_error, param);
ERRWRAP2(redirectError(OnError, last_on_error_param));
}
Py_RETURN_NONE;
}
最后,我们必须注册我们的“特殊方法” https://github.com/opencv/opencv/blob/3.4.1/modules/python/src2/cv2.cpp#L1684。与我们从中获得灵感的函数不同,我们不依赖于某些 GUI,并且我们希望它始终可用,因此我们将代码修改为如下所示:
static PyMethodDef special_methods[] = {
{"redirectError", (PyCFunction)pycvRedirectError, METH_VARARGS | METH_KEYWORDS, "redirectError(onError [, param]) -> None"},
#ifdef HAVE_OPENCV_HIGHGUI
{"createTrackbar", pycvCreateTrackbar, METH_VARARGS, "createTrackbar(trackbarName, windowName, value, count, onChange) -> None"},
{"createButton", (PyCFunction)pycvCreateButton, METH_VARARGS | METH_KEYWORDS, "createButton(buttonName, onChange [, userData, buttonType, initialButtonState]) -> None"},
{"setMouseCallback", (PyCFunction)pycvSetMouseCallback, METH_VARARGS | METH_KEYWORDS, "setMouseCallback(windowName, onMouse [, param]) -> None"},
#endif
{NULL, NULL},
};
现在我们可以重建 OpenCV、安装并测试它。我编写了以下脚本来演示功能:
import cv2
def verbose_error_handler(status, func_name, err_msg, file_name, line, userdata):
print "Status = %d" % status
print "Function = %s" % func_name
print "Message = %s" % err_msg
print "Location = %s(%d)" % (file_name, line)
print "User data = %r" % userdata
def silent_error_handler(status, func_name, err_msg, file_name, line, userdata):
pass
print "** Default handler"
try:
cv2.imshow("", None) # This causes an assert
except cv2.error as e:
pass
print "** Using verbose handler"
cv2.redirectError(verbose_error_handler, 42)
try:
cv2.imshow("", None) # This causes an assert
except cv2.error as e:
pass
print "** Using silent handler"
cv2.redirectError(silent_error_handler)
try:
cv2.imshow("", None) # This causes an assert
except cv2.error as e:
pass
print "** Back to default handler"
cv2.redirectError(None)
try:
cv2.imshow("", None) # This causes an assert
except cv2.error as e:
pass
使用我的 OpenCV 修补版本(基于上述说明),我在控制台上得到以下输出:
** Default handler
OpenCV(3.4.1) Error: Assertion failed (size.width>0 && size.height>0) in cv::imshow, file F:\GitHub\opencv\modules\highgui\src\window.cpp, line 364
** Using verbose handler
Status = -215
Function = cv::imshow
Message = size.width>0 && size.height>0
Location = F:\GitHub\opencv\modules\highgui\src\window.cpp(364)
User data = 42
** Using silent handler
** Back to default handler
OpenCV(3.4.1) Error: Assertion failed (size.width>0 && size.height>0) in cv::imshow, file F:\GitHub\opencv\modules\highgui\src\window.cpp, line 364
当我开始写这个答案时,我的想法之一是,自从默认错误处理程序 https://github.com/opencv/opencv/blob/fed22f2f5c17f28b07e56694825beb94f6574bcc/modules/core/src/system.cpp#L888使用以下格式化字符串将所有这些消息输出到stderr
"OpenCV(%s) Error: %s (%s) in %s, file %s, line %d"
我们也许可以钩住stderr
流,并过滤掉以常量前缀开头的任何行。唉,我没能实现这一目标。也许其他人可以?
编辑:修补合并到opencv:master
经过一些小的修改(主要是我们去掉了用户数据参数,因为它在 Python 中是不必要的)。