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