3

I've written a simple PyQt4 GUI that plays an OpenCV VideoCapture. This requires converting frames from numpy arrays to QImages. I'm using OpenCV so that I can detect circles using my findCircles method.

However, when I pass my frames to findCircles, the program crashes when the window is moved. This problem does not occur when I don't search for circles. I don't understand why this is happening, as I'm under the impression that the work is being done on a different thread than the GUI since I call findCircles from the run method of a QThread.

Note that I don't receive a normal error message in the console; Python crashes like such:

enter image description here

Here is the video file I've been using to test my player. I'm running Python 2.7.6 on Windows 8.1.

import sys
import cv2.cv as cv, cv2
from PyQt4.Qt import *
import time

def numpyArrayToQImage(array):
    if array != None:
        height, width, bytesPerComponent = array.shape
        bytesPerLine = bytesPerComponent * width;
        cv2.cvtColor(array, cv.CV_BGR2RGB, array)
        return QImage(array.data, width, height, bytesPerLine, QImage.Format_RGB888)
    return None

def findCircles(frame):
    grayFrame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    blurredFrame = cv2.medianBlur(grayFrame, 3)
    circles = cv2.HoughCircles(blurredFrame, cv.CV_HOUGH_GRADIENT, 1, 30, param1=50, param2=30, minRadius=30, maxRadius=35)

    if circles is not None: 
        for i in circles[0]:
            cv2.circle(frame, (i[0], i[1]), i[2], (255, 0, 0), 1) # Perimeter
            cv2.circle(frame, (i[0], i[1]), 3, (0, 255, 0), -1) # Center

class VideoThread(QThread):
    frameProcessed = pyqtSignal(QImage)

    def __init__(self, video, videoLabel):
        QThread.__init__(self)
        self.video = video
        self.fps = self.video.get(cv.CV_CAP_PROP_FPS)
        self.frameCount = self.video.get(cv.CV_CAP_PROP_FRAME_COUNT)
        self.startingSecond = 0
        self.videoLabel = videoLabel

    def run(self):
        clockAtStart = time.clock()

        while True:
            runtime = self.startingSecond + (time.clock() - clockAtStart)
            currentFrame = int(runtime * self.fps)

            if currentFrame < self.frameCount - 1:
                self.video.set(cv.CV_CAP_PROP_POS_FRAMES, currentFrame)
                frame = self.video.read()[1]
                findCircles(frame) # Removing this line removes the issue
                self.frameProcessed.emit(numpyArrayToQImage(frame))
                time.sleep(.02)
            else:
                break

class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.initUI()

    @pyqtSlot(QImage)
    def updateVideoLabel (self, image):
        self.videoLabel.setPixmap(QPixmap.fromImage(image))
        self.videoLabel.update()

    def initUI(self):
        self.setGeometry(300, 300, 500, 375)
        self.setMinimumHeight(250)
        self.createWidgets()
        self.addWidgets()

    def startNewVideo(self):
        self.video = cv2.VideoCapture(unicode(QFileDialog.getOpenFileName(self, "Open video").toUtf8(), encoding="UTF-8"))
        self.videoThread = VideoThread(self.video, self.videoLabel)
        self.videoThread.frameProcessed.connect(self.updateVideoLabel)
        self.playVideoFrom(0)

    def playVideoFrom(self, frame):
        self.videoThread.startingSecond = frame / self.videoThread.fps
        self.videoThread.start()

    def createWidgets(self):
        self.populateMenuBar()
        self.videoLabel = QLabel()
        self.videoLabel.setStyleSheet('background-color : black;');

    def populateMenuBar(self):
        self.menuBar = self.menuBar()
        fileMenu = QMenu('File', self)
        openAction = QAction('Open video...', self)
        openAction.triggered.connect(self.startNewVideo)
        fileMenu.addAction(openAction)
        self.menuBar.addMenu(fileMenu)

    def addWidgets(self):
        mainLayout = QVBoxLayout()
        mainLayout.addWidget(self.videoLabel, 1)
        centralWidget = QWidget()
        self.setCentralWidget(centralWidget)
        centralWidget.setLayout(mainLayout)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    player = MainWindow()
    player.show()
    sys.exit(app.exec_())
Joel Christophel
  • 2,604
  • 4
  • 30
  • 49
  • Did you try returning frame from the function findCircles? – wrdeman Feb 22 '15 at 21:56
  • The `findCircles` method draws the found circles on top of the frame, thus altering the frame object. There is no need to return anything. – Joel Christophel Feb 22 '15 at 22:01
  • 1
    CCan you provide an example video to test your program? – Ha Dang Feb 24 '15 at 09:24
  • I cannot reproduce reproduce the problem with the current example script. The code does seem able to find circles in a simple video showing some coins - although it is very inconsistent at doing so. Constantly moving the window whilst showing the video has no effect, though. This is on Linux, using python 2.7.9, qt 4.8.6, and pyqt 4.11.3. – ekhumoro Feb 24 '15 at 18:36
  • Try with python 2.7.9 – Ha Dang Feb 25 '15 at 12:40
  • You might have an unsafe-thread behavior. This [thread](http://stackoverflow.com/questions/21051710/pyqt-crashing-out-as-a-windows-appcrash) talks about it. Can you try putting time.sleep(.02) before the line self.frameProcessed.emit(numpyArrayToQImage(frame)) (and not after) and see if your app still crashes when moving the app window? – Ha Dang Feb 25 '15 at 13:56
  • @JoelA.Christophel. Can you get any useful debug information out of that silly dialog? Without that, it's very hard to say what the underlying problem might be. – ekhumoro Feb 26 '15 at 19:15
  • @JoelA.Christophel: Can you try your program on Linux? I suspect that Windows 8 is the problem. – Ha Dang Feb 27 '15 at 08:59
  • Does making a copy of array help at all? e.g. `self.video.read()[1].copy()` if that read isn't making a copy of the frame you'll end up with a frame object existing in both threads. It might be this combined with the refresh needed on drag is causing a segfault on the next read. – mfitzp Mar 01 '15 at 15:03
  • I've updated to Python 2.7.9 with no luck. – Joel Christophel Mar 18 '15 at 02:58

1 Answers1

0

I've tested your program, and it crashes when it finds no circles as indicated in the error message:

Traceback (most recent call last):
  File "test_opencv_tkinter.py", line 53, in run
    findCircles(frame) # Removing this line removes the issue
  File "test_opencv_tkinter.py", line 26, in findCircles
    if len(circles) > 0:
TypeError: object of type 'NoneType' has no len()

I've made some changes in the findCircles(frame) function, as follows, and it runs without error, even when I move the window around on the screen.

def findCircles(frame):
    grayFrame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    blurredFrame = cv2.medianBlur(grayFrame, 3)
    circles = cv2.HoughCircles(grayFrame,cv.CV_HOUGH_GRADIENT,1,20,
                            param1=50,param2=30,minRadius=0,maxRadius=0)
    if circles == None: 
        print "no circles found"
        return
    if len(circles) > 0:
        print "found circles ", len(circles[0])
        for i in circles[0]:
            cv2.circle(frame, (i[0], i[1]), i[2], (255, 0, 0), 1) # Perimeter
            cv2.circle(frame, (i[0], i[1]), 3, (0, 255, 0), -1) # Center
BartoszKP
  • 34,786
  • 15
  • 102
  • 130
Ha Dang
  • 1,218
  • 1
  • 10
  • 12
  • 1
    It turns out that in creating a short example of my problem, I introduced an error that was not originally in my code, which is what you found. I've edited my code slightly. Now there are no errors when no circles are found, but moving the window is still an issue. – Joel Christophel Feb 24 '15 at 16:02
  • Can you provide the crash message? What's your running environment? – Ha Dang Feb 24 '15 at 16:04