Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to stop OpenCV error message from printing in Python

Tags:

python

opencv

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?

like image 262
Pro Q Avatar asked Apr 01 '18 20:04

Pro Q


1 Answers

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, &param))
        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).

like image 83
Dan Mašek Avatar answered Sep 26 '22 19:09

Dan Mašek