Saturday, February 28, 2015

Getting image watch like debugging in Qt Creator

So a big sticking point for me with working on Linux was the lack of Image Watch which is a great extension for viewing OpenCV images in memory.  Without this functionality debugging can be a huge pain.

Luckily there is a reasonable solution that works nearly as well.

By using GDB's python scripting capabilities, we can download the image from the program that we're debugging and display it with OpenCV's python bindings in an OpenCV imshow window.

I really liked the ability to zoom in on a pixel and view the actual numeric value.  Luckily if you compile OpenCV with Qt and OpenGL, that functionality is available.  I'm not sure which one actually gives this functionality, because I just use both anyways.


Anyways on to the process:


Compile OpenCV with Qt, OpenGL and Python2.7 from source.

This creates a cv2.pyd file that contains the correct python bindings that we need.

Check if GDB is built against python 2 or python 3.  If it's built against python 3 we'll have some issues.  Primarily because OpenCV doesn't have great python 3 support yet.  You can do this in gdb by typing:

python print(sys.version_info)

If this says it is Python 3, we'll have to either build a version of gdb that is compiled against Python 2 or use a different gdb.  I ended up downloading gdb and building it from source against python 2 (all I did was set the python option to on with:

./configure --with-python

Alternatively, if you're using cuda-gdb it already is built against Python 2.

Next make sure gdb can load OpenCV's python module, inside gdb type:

python import cv2

If that works correctly, then hopefully you're accessing the correct build of opencv.  If it doesn't work, or it's the wrong version of OpenCV, take the cv2.pyd and cv2.so file that was compiled from source and put it in your gdb data-drectory. (This can be found from within gdb with "show data-directory")

Now to the fun part, dumping a cv::Mat in python and displaying it with cv2.imshow.

A nice little script was started by Asmwarrior which works for previous versions of OpenCV.

I've since updated it to work appropriately.  It isn't finished but I'll update this as I improve it.

***************************** Script Start **********************************

import gdb
import cv2
import sys
import numpy


class PlotterCommand(gdb.Command):
    def __init__(self):
        super(PlotterCommand, self).__init__("plot",
                                             gdb.COMMAND_DATA,
                                             gdb.COMPLETE_SYMBOL)
    def invoke(self, arg, from_tty):
        args = gdb.string_to_argv(arg)
     
     
        # generally, we type "plot someimage" in the GDB commandline
        # where "someimage" is an instance of cv::Mat
        v = gdb.parse_and_eval(args[0])
     
        # the value v is a gdb.Value object of C++
        # code's cv::Mat, we need to translate to
        # a python object under cv2.cv
        image_size =  (v['cols'],v['rows'])
        # print v
        # these two below lines do not work. I don't know why
        # channel = gdb.execute("call "+ args[0] + ".channels()", False, True)
        # channel = v.channels();
        CV_8U =0
        CV_8S =1
        CV_16U=2
        CV_16S=3
        CV_32S=4
        CV_32F=5
        CV_64F=6
        CV_USRTYPE1=7
        CV_CN_MAX = 512
        CV_CN_SHIFT = 3
        CV_MAT_CN_MASK = (CV_CN_MAX - 1) << CV_CN_SHIFT
        flags = v['flags']
        channel = (((flags) & CV_MAT_CN_MASK) >> CV_CN_SHIFT) + 1
        CV_DEPTH_MAX = (1 << CV_CN_SHIFT)
        CV_MAT_DEPTH_MASK = CV_DEPTH_MAX - 1
        depth = (flags) & CV_MAT_DEPTH_MASK
        IPL_DEPTH_SIGN = 0x80000000
        cv_elem_size = (((4<<28)|0x8442211) >> depth*4) & 15
        if (depth == CV_8S or depth == CV_16S or depth == CV_32S):
                mask = IPL_DEPTH_SIGN
        else:
                mask = 0
        ipl_depth = cv_elem_size*8 | mask  

     
        # conver the v['data'] type to "char*" type
        char_type = gdb.lookup_type("char")
        char_pointer_type =char_type.pointer()
        buffer = v['data'].cast(char_pointer_type)
     
        # read bytes from inferior's memory, because
        # we run the opencv-python module in GDB's own process
        # otherwise, we use memory corss processes      
        buf = v['step']['buf']
        bytes = buf[0] * v['rows'] # buf[0] is the step? Not quite sure.
        print('Reading image')
        inferior = gdb.selected_inferior()
        mem = inferior.read_memory(buffer, bytes)
        print('Read successful')
        if(depth == CV_8U):
            print('8bit image')
            img = numpy.frombuffer(mem, count=bytes, dtype=numpy.uint8)
        if(depth == CV_16U):
            print('16bit image')
            img = numpy.frombuffer(mem, count=bytes, dtype=numpy.uint16)
        img = img.reshape((v['rows'], v['cols'], channel))
        print(img.shape)
        # create a window, and show the image

        cv2.imshow("debugger", img)
     
        # the below statement is necessory, otherwise, the Window
        # will hang
        cv2.waitKey(0)
PlotterCommand()

***************************** Script End **********************************

Now we just edit .gdbinit to auto load the python script by adding the following to .gdbinit:

source {path to script}

And now we can view an opencv image in gdb with:

plot {variable name}


Now this isn't quite as nice as what it could be, QtCreator has built in image viewing functionality for QImage's.  Unfortunately I've already spent a good bit of time trying to get that to work, but with no luck.  So for now, I just keep the gdb console open in QtCreator and use this method.

Bugs:

Running into "Gtk-CRITICAL **: IA__gtk_widget_style_get: assertion `GTK_IS_WIDGET (widget)' failed" when trying to display an image.
Execute the following before opening QtCreator:
export LIBOVERLAY_SCROLLBAR=0


1 comment:

  1. Hello Daniel,

    I am trying to follow your steps to be able to run that code on the GDB. I am stuck with that Problem, that I can't "import cv2" on the GDB, eventhough I did the following :

    OpenCV was compiled with Qt, OpenGL and Python27 source (64-bit).

    But I don't find any files called cv2.pyd or cv2.so in the build opencv directory nor in the opencv source. The build files directory was also added to PATH in enviroment variables.


    What could I be missing ? I would appreciate any tip or hint from you, I have been trying to get it working over a month now :D

    Best regards,

    Anas Elwakil

    ReplyDelete