Same as this question here, except for Python, not C++.
I have an error message in OpenCV that is printing even if I do an except catch around it.
The solution to the linked question recommends using a redirect function, but this comment suggests that function does not exist in Python for OpenCV.
How can I stop an OpenCV error message from printing while still allowing me to print exactly what I want?
As of writing this answer (OpenCV 3.4.1 being the last released version), there is no way to just filter just the output of the default error handler (that I can think of), nor is there a way to change the error handler.
However, your question got me thinking -- in the highgui module, we already have several functions that let us set Python callbacks for mouse, trackbar and button events, so we could take inspiration from that code and patch this new functionality in.
Let's work with version 3.4.1. The file of interest is modules/python/src2/cv2.cpp
. We will begin by taking inspiration from functions OnMouse
and pycvSetMouseCallback
.
Let's make the Python error handler have a signature matching the C++ error handler:
error_handler([int]status, [str]func_name, [str]err_msg, [str]file_name, [int]line, [obj]userdata) -> None
Let's also add support to reset to the default error handler -- something the highgui
functions don't yet do.
First of all, we'll need a static error handler function, which will marshal arguments from C++ to Python, and call the appropriate Python function to handle the error. Just like the functions we're taking inspiration from, we'll use the user data parameter to hold a tuple consisting of a function object along with optional Python user data).
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
}
Next, we'll need to write the function to implement the binding between Python and C++. However, due to my suspicions of potential memory leaks in the functions we're taking inspiration from, we'll make some additions to fix that -- we'll keep track of the most recent user data object and dereference it as necessary.
// 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;
}
Finally, we have to register our "special method". Unlike the functions we took inspiration from we don't depend on some GUI and we want this to be always available, so we'll modify the code to look as follows:
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},
};
Now we can rebuild OpenCV, install it and test it out. I wrote the following script to demonstrate the functionality:
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
With my patched version of OpenCV (based on the above instructions), I get the following output on my console:
** 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
One of my thoughts just as I began writing this answer was that since the default error handler uses the following formatting string to output all of those messages to stderr
"OpenCV(%s) Error: %s (%s) in %s, file %s, line %d"
we could perhaps hook the stderr
stream, and filter out any lines that begin with the constant prefix. Alas, I was not able to achieve this. Perhaps someone else can?
Edit: Patched merged into opencv:master
with some minor modifications (mainly we got rid of the user data parameter, since it's unnecessary in Python).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With