Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
145 views
in Technique[技术] by (71.8m points)

python - Capture coordinates of a point w.r.t. Video frame

I have a made a GUI application using PyQt where I display a camera feed on the monitor. The frame size which I receive from the camera differs from my screen resolution.

I have below code which maps my camera frame on my screen so that when I click a point, it gives me the coordinates with respect to my video frame.

from collections import deque
from datetime import datetime
import sys
from threading import Thread
import time

import cv2

from PyQt4 import QtCore, QtGui


class CameraWidget(QtGui.QGraphicsView):
    """Independent camera feed
    Uses threading to grab IP camera frames in the background

    @param width - Width of the video frame
    @param height - Height of the video frame
    @param stream_link - IP/RTSP/Webcam link
    @param aspect_ratio - Whether to maintain frame aspect ratio or force into fraame
    """

    def __init__(
        self,
        width,
        height,
        stream_link=0,
        aspect_ratio=False,
        parent=None,
        deque_size=1,
    ):
        super(CameraWidget, self).__init__(parent)

        # Initialize deque used to store frames read from the stream
        self.deque = deque(maxlen=deque_size)

        self.screen_width = width
        self.screen_height = height
        self.maintain_aspect_ratio = aspect_ratio

        self.camera_stream_link = stream_link

        # Flag to check if camera is valid/working
        self.online = False
        self.capture = None

        self.setScene(QtGui.QGraphicsScene(self))

        self._pixmap_item = self.scene().addPixmap(QtGui.QPixmap())

        self.load_network_stream()

        # Start background frame grabbing
        self.get_frame_thread = Thread(target=self.get_frame, args=())
        self.get_frame_thread.daemon = True
        self.get_frame_thread.start()

        # Periodically set video frame to display
        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.set_frame)
        self.timer.start(0.5)

        print("Started camera: {}".format(self.camera_stream_link))

    def load_network_stream(self):
        """Verifies stream link and open new stream if valid"""

        def load_network_stream_thread():
            if self.verify_network_stream(self.camera_stream_link):
                self.capture = cv2.VideoCapture(self.camera_stream_link)
                self.online = True

        self.load_stream_thread = Thread(target=load_network_stream_thread, args=())
        self.load_stream_thread.daemon = True
        self.load_stream_thread.start()

    def verify_network_stream(self, link):
        """Attempts to receive a frame from given link"""

        cap = cv2.VideoCapture(link)
        if not cap.isOpened():
            return False
        cap.release()
        return True

    def get_frame(self):
        """Reads frame, resizes, and converts image to pixmap"""

        while True:
            try:
                if self.capture.isOpened() and self.online:
                    # Read next frame from stream and insert into deque
                    status, frame = self.capture.read()
                    if status:
                        self.deque.append(frame)
                    else:
                        self.capture.release()
                        self.online = False
                else:
                    # Attempt to reconnect
                    print("attempting to reconnect", self.camera_stream_link)
                    self.load_network_stream()
                    self.spin(2)
                self.spin(0.001)
            except AttributeError:
                pass

    def spin(self, seconds):
        """Pause for set amount of seconds, replaces time.sleep so program doesnt stall"""

        time_end = time.time() + seconds
        while time.time() < time_end:
            QtGui.QApplication.processEvents()

    def set_frame(self):
        """Sets pixmap image to video frame"""

        if not self.online:
            self.spin(1)
            return

        if self.deque and self.online:
            # Grab latest frame
            frame = self.deque[-1]

            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            h, w, ch = frame.shape
            bytesPerLine = ch * w

            # Convert to pixmap and set to video frame
            image = QtGui.QImage(frame, w, h, bytesPerLine, QtGui.QImage.Format_RGB888)
            pixmap = QtGui.QPixmap.fromImage(image.copy())
            self._pixmap_item.setPixmap(pixmap)
        self.fix_size()

    def mousePressEvent(self, event):
        super(CameraWidget, self).mousePressEvent(event)
        vp = event.pos()
        it = self.itemAt(vp)
        if it == self._pixmap_item:
            sp = self.mapToScene(vp)
            lp = it.mapFromScene(sp)
            print(lp.toPoint().x(), lp.toPoint().y())

    def resizeEvent(self, event):
        self.fix_size()
        super().resizeEvent(event)

    def fix_size(self):
        self.fitInView(
            self._pixmap_item,
            QtCore.Qt.KeepAspectRatio
            if self.maintain_aspect_ratio
            else QtCore.Qt.IgnoreAspectRatio,
        )


class Window(QtGui.QWidget):
    def __init__(self, cam=None, parent=None):
        super(Window, self).__init__(parent)

        self.showMaximized()

        self.screen_width = self.width()
        self.screen_height = self.height()

        # Create camera widget
        print("Creating Camera Widget...")
        self.camera = CameraWidget(self.screen_width, self.screen_height, cam)

        lay = QtGui.QVBoxLayout(self)
        lay.setContentsMargins(0, 0, 0, 0)
        lay.setSpacing(0)
        lay.addWidget(self.camera)


camera = 'rtsp://admin:vaaan@[email protected]/Streaming/Channels/2'

if __name__ == "__main__":
    app = QtGui.QApplication([])
    win = Window(camera)
    sys.exit(app.exec_())

Now, I wanna draw 4 points on this video frame using numpy and then drag & move those points to any location I desire on the video frame. I want to retrieve the coordinates of the location the point is moved to (when the mouse button is released).

Now the problem is that though I'm able to draw and move points on the video frame using the below code, the coordinates which I receive are w.r.t. to the screen resolution and not the size of the video frame. Instead, I want the new coordinates w.r.t. to frame size only.

from PyQt4 import QtGui,QtCore
import numpy as np

class Canvas(QtGui.QWidget):

    DELTA = 10 #for the minimum distance        

    def __init__(self, parent):
        super(Canvas, self).__init__(parent)
        self.draggin_idx = -1        
        # self.setGeometry(0,0,200,200)
        self.points = np.array([[v*5,v*5] for v in range(1,17, 5)], dtype=np.float)        

    def paintEvent(self, e):
        qp = QtGui.QPainter()
        qp.begin(self)
        self.drawPoints(qp)
        qp.end()

    def drawPoints(self, qp):
        pen = QtGui.QPen()
        pen.setWidth(8)
        pen.setColor(QtGui.QColor('red'))
        qp.setPen(pen)
        for x,y in self.points:
            qp.drawPoint(x,y)        

    def _get_point(self, evt):
        return np.array([evt.pos().x(),evt.pos().y()])

    #get the click coordinates
    def mousePressEvent(self, evt):
        if evt.button() == QtCore.Qt.LeftButton and self.draggin_idx == -1:
            point = self._get_point(evt)
            #dist will hold the square distance from the click to the points
            dist = self.points - point
            dist = dist[:,0]**2 + dist[:,1]**2
            dist[dist>self.DELTA] = np.inf #obviate the distances above DELTA
            if dist.min() < np.inf:
                self.draggin_idx = dist.argmin()        

    def mouseMoveEvent(self, evt):
        if self.draggin_idx != -1:
            point = self._get_point(evt)
            self.points[self.draggin_idx] = point
            self.update()

    def mouseReleaseEvent(self, evt):
        if evt.button() == QtCore.Qt.LeftButton and self.draggin_idx != -1:
            point = self._get_point(evt)
            print(point)                            # This gives me new location of the point.
            self.points[self.draggin_idx] = point
            self.draggin_idx = -1
            self.update()        

app = QtGui.QApplication([])

c = Canvas(None)
c.showMaximized()
app.exec_()
question from:https://stackoverflow.com/questions/65892085/capture-coordinates-of-a-point-w-r-t-video-frame

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)
Waitting for answers

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...