Import LinuxCNC source tree
This commit is contained in:
768
lib/python/qtvcp/widgets/camview_widget.py
Normal file
768
lib/python/qtvcp/widgets/camview_widget.py
Normal file
@@ -0,0 +1,768 @@
|
||||
#!/usr/bin/env python3
|
||||
# Qtvcp camview
|
||||
#
|
||||
# Copyright (c) 2017 Chris Morley <chrisinnanaimo@hotmail.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# use open cv to do camera alignment
|
||||
|
||||
import os
|
||||
import _thread as Thread
|
||||
|
||||
import hal
|
||||
|
||||
from qtpy import QtWidgets, QtCore
|
||||
from qtpy.QtGui import QColor, QFont, QPainter, QPen, QImage
|
||||
|
||||
from qtvcp.widgets.widget_baseclass import _HalWidgetBase
|
||||
from qtvcp import logger
|
||||
|
||||
# Instiniate the libraries with global reference
|
||||
# STATUS gives us status messages from linuxcnc
|
||||
# LOG is for running code logging
|
||||
if __name__ != '__main__': # This avoids segfault when testing directly in python
|
||||
from qtvcp.core import Status, Info
|
||||
STATUS = Status()
|
||||
INFO = Info()
|
||||
LOG = logger.getLogger(__name__)
|
||||
#LOG.setLevel(logger.VERBOSE) # One of VERBOSE, DEBUG, INFO, WARNING, ERROR, CRITICAL
|
||||
|
||||
# Suppress cryptic messages when checking for useable ports
|
||||
os.environ["OPENCV_LOG_LEVEL"]="FATAL"
|
||||
|
||||
# If the library is missing don't crash the GUI
|
||||
# send an error and just make a blank widget.
|
||||
LIB_GOOD = True
|
||||
try:
|
||||
import cv2 as CV
|
||||
DEFAULT_API = CV.CAP_ANY
|
||||
except:
|
||||
LOG.error('Qtvcp Error with camview - is python3-opencv installed?')
|
||||
LIB_GOOD = False
|
||||
DEFAULT_API = 0 # CV.CAP_ANY
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
|
||||
class CamView(QtWidgets.QWidget, _HalWidgetBase):
|
||||
def __init__(self, parent=None):
|
||||
super(CamView, self).__init__(parent)
|
||||
self._qImageFormat=QImage.Format_RGB888
|
||||
self.video = None
|
||||
self.grabbed = None
|
||||
self.frame = None
|
||||
self._camNum = 0
|
||||
self.api = DEFAULT_API
|
||||
self.resolution=None
|
||||
self.diameter = 20
|
||||
self.rotation = 0
|
||||
self.rotationIncrement = .5
|
||||
self.scale = 1
|
||||
self.scaleX = 1.0
|
||||
self.scaleY = 1.0
|
||||
self.gap = 5
|
||||
self._noRotate = False
|
||||
self.setWindowTitle('Cam View')
|
||||
self.setGeometry(100, 100, 200, 200)
|
||||
self.text_color = QColor(255, 255, 255)
|
||||
self.circle_color = QtCore.Qt.red
|
||||
self.cross_color = QtCore.Qt.yellow
|
||||
self.cross_pointer_color = QtCore.Qt.white
|
||||
self.font = QFont("arial,helvetica", 40)
|
||||
self.SUB = str.maketrans("0123456789", "₀₁₂₃₄₅₆₇₈₉")
|
||||
if LIB_GOOD:
|
||||
self.text = 'No Image'
|
||||
else:
|
||||
self.text = 'Missing\npython-opencv\nLibrary'
|
||||
self.pix = None
|
||||
self.degree = str("\N{DEGREE SIGN}")
|
||||
|
||||
# trap so can run script directly to test
|
||||
try:
|
||||
if INFO.PROGRAM_PREFIX is not None:
|
||||
self.user_path = os.path.expanduser(INFO.PROGRAM_PREFIX)
|
||||
except:
|
||||
self.user_path = (os.path.join(os.path.expanduser('~'), 'linuxcnc/nc_files'))
|
||||
|
||||
#self.blobInit()
|
||||
|
||||
def _hal_init(self):
|
||||
try:
|
||||
self.pin_ = self.HAL_GCOMP_.newpin('cam-rotation',hal.HAL_FLOAT, hal.HAL_OUT)
|
||||
except:
|
||||
pass
|
||||
if LIB_GOOD:
|
||||
STATUS.connect('periodic', self.nextFrameSlot)
|
||||
|
||||
##################################
|
||||
# no button scroll = circle diameter
|
||||
# left button scroll = zoom
|
||||
# right button scroll = cross hair rotation
|
||||
##################################
|
||||
def wheelEvent(self, event):
|
||||
super(CamView, self).wheelEvent(event)
|
||||
mouse_state = QtWidgets.QApplication.mouseButtons()
|
||||
if event.angleDelta().y() < 0:
|
||||
if mouse_state == QtCore.Qt.NoButton:
|
||||
self.diameter -= self.rotationIncrement
|
||||
if mouse_state == QtCore.Qt.LeftButton:
|
||||
self.scale -= .1
|
||||
if mouse_state == QtCore.Qt.RightButton:
|
||||
if not self._noRotate:
|
||||
self.rotation += self.rotationIncrement
|
||||
else:
|
||||
if mouse_state == QtCore.Qt.NoButton:
|
||||
self.diameter += self.rotationIncrement
|
||||
if mouse_state == QtCore.Qt.LeftButton:
|
||||
self.scale += .1
|
||||
if mouse_state == QtCore.Qt.RightButton:
|
||||
if not self._noRotate:
|
||||
self.rotation -= self.rotationIncrement
|
||||
self.limitChecks()
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
if event.button() & QtCore.Qt.LeftButton:
|
||||
if not self._noRotate:
|
||||
self.rotation += self.rotationIncrement
|
||||
self.limitChecks()
|
||||
elif event.button() & QtCore.Qt.RightButton:
|
||||
if not self._noRotate:
|
||||
self.rotation -= self.rotationIncrement
|
||||
self.limitChecks()
|
||||
elif event.button() & QtCore.Qt.MiddleButton:
|
||||
self.rotation_increments_changed()
|
||||
|
||||
def mouseDoubleClickEvent(self, event):
|
||||
if event.button() & QtCore.Qt.LeftButton:
|
||||
self.scale = 1
|
||||
elif event.button() & QtCore.Qt.RightButton:
|
||||
self.rotation = 0
|
||||
elif event.button() & QtCore.Qt.MiddleButton:
|
||||
self.diameter = 20
|
||||
|
||||
def zoom_in(self):
|
||||
if self.scale >= 5:
|
||||
return
|
||||
self.scale += 0.1
|
||||
|
||||
def zoom_out(self, event):
|
||||
if self.scale <= 1:
|
||||
return
|
||||
self.scale -= 0.1
|
||||
|
||||
def limitChecks(self):
|
||||
w = self.size().width()
|
||||
if self.diameter < 2: self.diameter = 2
|
||||
if self.diameter > w: self.diameter = w
|
||||
if self.rotation > 360 - self.rotationIncrement: self.rotation = 0
|
||||
if self.rotation < 0: self.rotation = 360 - self.rotationIncrement
|
||||
if self.scale < 1: self.scale = 1
|
||||
if self.scale > 5: self.scale = 5
|
||||
|
||||
def nextFrameSlot(self, w):
|
||||
if not self.video: return
|
||||
if not self.isVisible(): return
|
||||
|
||||
############################
|
||||
# capture a freme from cam
|
||||
############################
|
||||
ret, frame = self.video.read()
|
||||
if not ret: return
|
||||
|
||||
# set digital zoom
|
||||
frame = self.zoom(frame, self.scale)
|
||||
|
||||
# invert image
|
||||
frame = self.flip(frame)
|
||||
|
||||
# make a Q image
|
||||
self.pix = self.makeImage(frame, self._qImageFormat)
|
||||
|
||||
# make a CV window for testing
|
||||
#self.makeCVImage(self.canny(self.convertToGray(frame)))
|
||||
|
||||
# repaint the window
|
||||
self.update()
|
||||
try:
|
||||
self.pin_.set(self.rotation)
|
||||
except:
|
||||
pass
|
||||
|
||||
def convertToRGB(self, img):
|
||||
return CV.cvtColor(img, CV.COLOR_BGR2RGB)
|
||||
|
||||
# new qt 5.13 + for QImage.Format_Grayscale16
|
||||
def convertToGray(self, img):
|
||||
return CV.cvtColor(img, CV.COLOR_BGR2GRAY)
|
||||
|
||||
def blur(self, img, B=7):
|
||||
return CV.GaussingBlur(img, (B,B), CV.BORDER_DEFAULT)
|
||||
|
||||
def canny(self, img, x=125, y=175):
|
||||
return CV.Canny(img , x, y)
|
||||
|
||||
def makeImage(self, image, qFormat=QImage.Format_RGB888):
|
||||
img = self.convertToRGB(image)
|
||||
return QImage(img, img.shape[1], img.shape[0], img.strides[0], qFormat)
|
||||
|
||||
# openCV Window
|
||||
def makeCVImage(self, frame):
|
||||
CV.imshow('CV Image',frame)
|
||||
|
||||
def rescaleFrame(self, frame, scale=1, scale_x=1.0, scale_y=1.0):
|
||||
x = abs(scale_x * scale)
|
||||
y = abs(scale_y * scale)
|
||||
return CV.resize(frame, None, fx = x, fy = y, interpolation=CV.INTER_CUBIC)
|
||||
|
||||
def zoom(self, frame, scale):
|
||||
# get original size of image
|
||||
(oh, ow) = frame.shape[:2]
|
||||
#############################
|
||||
# scale image
|
||||
#############################
|
||||
frame = self.rescaleFrame(frame, scale,self.scaleX,self.scaleY)
|
||||
##########################
|
||||
# crop to the original size of the frame
|
||||
# measure from center, so we zoom on center
|
||||
# ch = center of current height
|
||||
# coh = center of original height
|
||||
##########################
|
||||
(h, w) = frame.shape[:2]
|
||||
ch = int(h/2)
|
||||
cw = int(w/2)
|
||||
coh = int(oh/2)
|
||||
cow = int(ow/2)
|
||||
# NOTE: its img[y: y + h, x: x + w]
|
||||
return frame[ch-coh:ch+coh, cw-cow:cw+cow]
|
||||
|
||||
# flip the image based on if scaleX and scaleY are negative
|
||||
def flip(self, image):
|
||||
if self.scaleX > 0 and self.scaleY > 0:
|
||||
return image
|
||||
elif self.scaleX < 0 and self.scaleY < 0:
|
||||
flip = -1
|
||||
elif self.scaleX < 0:
|
||||
flip = 0
|
||||
else:
|
||||
flip = 1
|
||||
return CV.flip(image, flip)
|
||||
|
||||
# draw a circle around small holes
|
||||
#
|
||||
def findCircles(self,frame):
|
||||
# Our operations on the frame come here
|
||||
gray = CV.cvtColor(frame, CV.COLOR_BGR2GRAY)
|
||||
# Display the resulting frame
|
||||
|
||||
circles = CV.HoughCircles(gray,CV.cv.CV_HOUGH_GRADIENT,1,20,param1=50,param2=30,minRadius=10,maxRadius=15)
|
||||
# print circles
|
||||
if circles is not None:
|
||||
#print circles
|
||||
circles = np.uint16(np.around(circles))
|
||||
for i in circles[0,:]:
|
||||
# draw the outer circle
|
||||
CV.circle(gray,(i[0],i[1]),i[2],(246,11,11),1)
|
||||
# draw the center of the circle
|
||||
CV.circle(gray,(i[0],i[1]),2,(246,11,11),1)
|
||||
|
||||
CV.imshow('Circles',gray)
|
||||
|
||||
def blobInit(self):
|
||||
# Setup BlobDetector
|
||||
detector = CV.SimpleBlobDetector()
|
||||
params = CV.SimpleBlobDetector_Params()
|
||||
|
||||
# Filter by Area.
|
||||
params.filterByArea = True
|
||||
params.minArea = 20000
|
||||
params.maxArea = 40000
|
||||
|
||||
# Filter by Circularity
|
||||
params.filterByCircularity = True
|
||||
params.minCircularity = 0.5
|
||||
|
||||
# Filter by Convexity
|
||||
params.filterByConvexity = False
|
||||
#params.minConvexity = 0.87
|
||||
|
||||
# Filter by Inertia
|
||||
params.filterByInertia = True
|
||||
params.minInertiaRatio = 0.8
|
||||
|
||||
# Distance Between Blobs
|
||||
params.minDistBetweenBlobs = 200
|
||||
|
||||
# Create a detector with the parameters
|
||||
self.detector = CV.SimpleBlobDetector(params)
|
||||
|
||||
# find circles overlay and draw a cross
|
||||
# https://www.kurokesu.com/main/2016/07/25/advanced-opencv-3-python-hole-detection/
|
||||
def findBlob(self, image):
|
||||
overlay = image.copy()
|
||||
|
||||
keypoints = self.detector.detect(image)
|
||||
for k in keypoints:
|
||||
CV.circle(overlay, (int(k.pt[0]), int(k.pt[1])), int(k.size/2), (0, 0, 255), -1)
|
||||
CV.line(overlay, (int(k.pt[0])-20, int(k.pt[1])), (int(k.pt[0])+20, int(k.pt[1])), (0,0,0), 3)
|
||||
CV.line(overlay, (int(k.pt[0]), int(k.pt[1])-20), (int(k.pt[0]), int(k.pt[1])+20), (0,0,0), 3)
|
||||
|
||||
opacity = 0.5
|
||||
CV.addWeighted(overlay, opacity, image, 1 - opacity, 0, image)
|
||||
|
||||
# Uncomment to resize to fit output window if needed
|
||||
#image = CV.resize(im, None,fx=0.5, fy=0.5, interpolation = CV.INTER_CUBIC)
|
||||
CV.imshow("Output", image)
|
||||
|
||||
def showEvent(self, event):
|
||||
if LIB_GOOD:
|
||||
if self.video is None:
|
||||
try:
|
||||
self.video = WebcamVideoStream(src=self._camNum, api=self.api, res = self.resolution )
|
||||
if not self.video.isOpened():
|
||||
p = self.video.list_ports(api=self.api)[1]
|
||||
self.text = 'Error with video {}\nAvailable ports:\n{}'.format(self._camNum, p)
|
||||
else:
|
||||
self.video.start()
|
||||
except Exception as e:
|
||||
LOG.error('Video capture error: {}'.format(e))
|
||||
else:
|
||||
self.video.start()
|
||||
|
||||
def hideEvent(self, event):
|
||||
if LIB_GOOD:
|
||||
try:
|
||||
self.video.stop()
|
||||
except:
|
||||
pass
|
||||
|
||||
def resizeEvent(self, event):
|
||||
# Create a square base size of 10x10 and scale it to the new size
|
||||
# maintaining aspect ratio.
|
||||
new_size = QtCore.QSize(10, 10)
|
||||
new_size.scale(event.size(), QtCore.Qt.KeepAspectRatio)
|
||||
self.resize(new_size)
|
||||
|
||||
def paintEvent(self, event):
|
||||
qp = QPainter()
|
||||
qp.begin(self)
|
||||
if self.pix:
|
||||
qp.drawImage(self.rect(), self.pix)
|
||||
self.drawText(event, qp)
|
||||
self.drawCircle(event, qp)
|
||||
self.drawCrossHair(event, qp)
|
||||
qp.end()
|
||||
|
||||
def drawText(self, event, qp):
|
||||
size = self.size()
|
||||
w = size.width()
|
||||
h = size.height()
|
||||
qp.setPen(self.text_color)
|
||||
qp.setFont(self.font)
|
||||
if self._noRotate:
|
||||
inc =''
|
||||
else:
|
||||
inc = str(self.rotationIncrement).translate(self.SUB)
|
||||
if self.pix:
|
||||
qp.drawText(self.rect(), QtCore.Qt.AlignTop, '{:.1f}{} {:>}'.format(self.rotation,self.degree,
|
||||
inc))
|
||||
else:
|
||||
qp.drawText(self.rect(), QtCore.Qt.AlignCenter, self.text)
|
||||
|
||||
def drawCircle(self, event, gp):
|
||||
size = self.size()
|
||||
w = size.width()
|
||||
h = size.height()
|
||||
radx = self.diameter/2
|
||||
rady = self.diameter/2
|
||||
# draw red circles
|
||||
gp.setPen(self.circle_color)
|
||||
center = QtCore.QPoint(w//2, h//2)
|
||||
gp.drawEllipse(center, radx, rady)
|
||||
|
||||
def drawCrossHair(self, event, gp):
|
||||
size = self.size()
|
||||
w = size.width()//2
|
||||
h = size.height()//2
|
||||
pen0 = QPen(self.cross_pointer_color, 1, QtCore.Qt.SolidLine)
|
||||
pen = QPen(self.cross_color, 1, QtCore.Qt.SolidLine)
|
||||
gp.translate(w, h)
|
||||
gp.rotate(-self.rotation)
|
||||
gp.setPen(pen0)
|
||||
gp.drawLine(0, 0-self.gap, 0, -h)
|
||||
gp.setPen(pen)
|
||||
gp.drawLine(-w, 0, 0-self.gap, 0)
|
||||
gp.drawLine(0+self.gap, 0, w, 0)
|
||||
gp.drawLine(0, 0+self.gap, 0, h)
|
||||
|
||||
def rotation_increments_changed(self,w=None):
|
||||
if self.rotationIncrement == 5.00:
|
||||
self.rotationIncrement = 1
|
||||
elif self.rotationIncrement == 1:
|
||||
self.rotationIncrement = 0.5
|
||||
elif self.rotationIncrement == 0.5:
|
||||
self.rotationIncrement = 0.1
|
||||
|
||||
else:
|
||||
self.rotationIncrement = 5
|
||||
|
||||
def setCircleColor(self, color):
|
||||
self.circle_color = color
|
||||
|
||||
def setCrossColor(self, color):
|
||||
self.cross_color = color
|
||||
|
||||
def setPointerColor(self, color):
|
||||
self.cross_pointer_color = color
|
||||
|
||||
def saveImage(self):
|
||||
filepath = '{}/camImage.png'.format(self.user_path)
|
||||
self.video.writeFrame(filepath)
|
||||
|
||||
# if data is a string or int, search for name/int in library and use it.
|
||||
def setAPI(self, data):
|
||||
if not LIB_GOOD:
|
||||
return
|
||||
camera_backends = CV.videoio_registry.getCameraBackends()
|
||||
|
||||
if isinstance(data,str):
|
||||
# should we use any backend option?
|
||||
if data in ('','ANY','DEFAULT'):
|
||||
self.api = 0
|
||||
return
|
||||
# is it a string of a number?
|
||||
if data.isdigit():
|
||||
if int(data) in camera_backends:
|
||||
self.api = int(data)
|
||||
else:
|
||||
LOG.warning(f'Backend {data} not found.')
|
||||
return
|
||||
# ok it's a name
|
||||
for backend in camera_backends:
|
||||
if CV.videoio_registry.getBackendName(backend) == data:
|
||||
self.api = backend
|
||||
break
|
||||
else:
|
||||
LOG.warning(f'Backend {data} not found.')
|
||||
|
||||
elif isinstance(data,int):
|
||||
if data in camera_backends:
|
||||
self.api = data
|
||||
else:
|
||||
LOG.warning(f'Backend {data} not found.')
|
||||
|
||||
# returns the api backend name from backend int
|
||||
def getAPIName(self, num):
|
||||
return CV.videoio_registry.getBackendName(self.api)
|
||||
|
||||
# sets the resolution tuple (width,height) from a string 'width,height'
|
||||
def setResolution(self, data):
|
||||
if isinstance(data,str):
|
||||
if not data.upper() in('','DEFAULT','NONE'):
|
||||
data = tuple(int(x) for x in data.split(','))
|
||||
self.resolution = data
|
||||
|
||||
#########################################################################
|
||||
# This is how designer can interact with our widget properties.
|
||||
# designer will show the Property properties in the editor
|
||||
# it will use the get set and reset calls to do those actions
|
||||
#
|
||||
# These can also be set as WIDGET.setProperty('property_name', data)
|
||||
########################################################################
|
||||
|
||||
|
||||
def set_wheel_rotation(self, value):
|
||||
self._noRotate = value
|
||||
def get_wheel_rotation(self):
|
||||
return self._noRotate
|
||||
def reset_wheel_rotation(self):
|
||||
self._noRotate = False
|
||||
def set_camnum(self, value):
|
||||
self._camNum = value
|
||||
def get_camnum(self):
|
||||
return self._camNum
|
||||
def reset_camnum(self):
|
||||
self._camNum = 0
|
||||
# designer will show these properties in this order:
|
||||
block_wheel_rotation = QtCore.Property(bool, get_wheel_rotation, set_wheel_rotation, reset_wheel_rotation)
|
||||
camera_number = QtCore.Property(int, get_camnum, set_camnum, reset_camnum)
|
||||
|
||||
class WebcamVideoStream:
|
||||
def __init__(self, src=0, api=DEFAULT_API, res=None):
|
||||
self.possible_resolutions = []
|
||||
# initialize the video camera stream and read the first frame
|
||||
# from the stream
|
||||
|
||||
flag = True
|
||||
camera_backends = CV.videoio_registry.getCameraBackends()
|
||||
for backend in camera_backends:
|
||||
if backend == api: flag = False
|
||||
LOG.verbose('Available backend library:{}'.format(CV.videoio_registry.getBackendName(backend)))
|
||||
if flag and api != CV.CAP_ANY:
|
||||
LOG.error('OpenCV backend library {} not available'.format(CV.videoio_registry.getBackendName(api)))
|
||||
|
||||
LOG.debug("Using {} backend".format(CV.videoio_registry.getBackendName(api)))
|
||||
|
||||
self.stream = self.openStream(src, api, res)
|
||||
|
||||
if not (self.stream.isOpened()):
|
||||
self.stream.release()
|
||||
LOG.error('Could not open video device {}'.format(src))
|
||||
|
||||
# get a list of ports
|
||||
plist = self.list_ports(api)[1]
|
||||
if src not in plist:
|
||||
LOG.error('port number {}, is not a working port- trying: {}'.format(src,plist[0]))
|
||||
# try again with a hopefully functioning port
|
||||
if plist != []:
|
||||
self.stream = self.openStream(plist[0], api, res)
|
||||
|
||||
|
||||
width = int(self.stream.get(CV.CAP_PROP_FRAME_WIDTH))
|
||||
height = int(self.stream.get(CV.CAP_PROP_FRAME_HEIGHT))
|
||||
LOG.info(f"Current camera resolution: {width}x{height}")
|
||||
|
||||
# initialize the variable used to indicate if the thread should
|
||||
# be stopped
|
||||
self.stopped = False
|
||||
self.grabbed = None
|
||||
self.frame = None
|
||||
|
||||
def isOpened(self):
|
||||
try:
|
||||
return self.stream.isOpened()
|
||||
except:
|
||||
return False
|
||||
|
||||
def openStream(self, src, api, res=None):
|
||||
# initialize the video camera stream and read the first frame
|
||||
# from the stream
|
||||
try:
|
||||
stream = CV.VideoCapture(src, api)
|
||||
except Exception as e:
|
||||
log.debug(e)
|
||||
# try using an older function signature
|
||||
stream = CV.VideoCapture(src)
|
||||
|
||||
# seems only val2 is successful changing resolutions
|
||||
if not api == CV.CAP_ANY:
|
||||
w,h = self.getBestResolution(stream)
|
||||
# look for requested resolution
|
||||
if not res is None and res in self.possible_resolutions:
|
||||
w,h=res
|
||||
stream.set(CV.CAP_PROP_FRAME_WIDTH, w)
|
||||
stream.set(CV.CAP_PROP_FRAME_HEIGHT, h)
|
||||
|
||||
# last ditch - sometimes the default resolution can not be changed
|
||||
# if the height is 0 - failed, so just try default
|
||||
actual_height = int(stream.get(CV.CAP_PROP_FRAME_HEIGHT))
|
||||
if actual_height == 0:
|
||||
stream.release()
|
||||
stream = CV.VideoCapture(src, api)
|
||||
|
||||
return stream
|
||||
|
||||
def getBestResolution(self, stream):
|
||||
resolutions_to_test = [
|
||||
(320, 240), (640, 480), (800, 600), (1024, 768),
|
||||
(1280, 720), (1920, 1080)
|
||||
]
|
||||
start_width = int(stream.get(CV.CAP_PROP_FRAME_WIDTH))
|
||||
start_height = int(stream.get(CV.CAP_PROP_FRAME_HEIGHT))
|
||||
|
||||
for width, height in resolutions_to_test:
|
||||
stream.set(CV.CAP_PROP_FRAME_WIDTH, width)
|
||||
stream.set(CV.CAP_PROP_FRAME_HEIGHT, height)
|
||||
actual_width = int(stream.get(CV.CAP_PROP_FRAME_WIDTH))
|
||||
actual_height = int(stream.get(CV.CAP_PROP_FRAME_HEIGHT))
|
||||
|
||||
if actual_width == width and actual_height == height:
|
||||
if not (actual_width,actual_height) in self.possible_resolutions:
|
||||
self.possible_resolutions.append((actual_width,actual_height))
|
||||
elif actual_width !=0 and actual_height !=0:
|
||||
self.possible_resolutions.append((actual_width,actual_height))
|
||||
else:
|
||||
LOG.verbose(f"Resolution {width}x{height} is NOT supported (actual: {actual_width}x{actual_height}).")
|
||||
|
||||
LOG.verbose("All possible resolutions found:")
|
||||
for res in self.possible_resolutions:
|
||||
LOG.verbose(res)
|
||||
try:
|
||||
best = self.possible_resolutions[len(self.possible_resolutions)-1]
|
||||
LOG.debug('Best resolution:{}'.format(best))
|
||||
except:
|
||||
return (start_width,start_height)
|
||||
return best
|
||||
|
||||
def start(self):
|
||||
# start the thread to read frames from the video stream
|
||||
self.stopped = False
|
||||
Thread.start_new_thread(self._update, ())
|
||||
|
||||
return self
|
||||
|
||||
def _update(self):
|
||||
# keep looping infinitely until the thread is stopped
|
||||
while True:
|
||||
# if the thread indicator variable is set, stop the thread
|
||||
if self.stopped:
|
||||
return
|
||||
# otherwise, read the next frame from the stream
|
||||
(self.grabbed, self.frame) = self.stream.read()
|
||||
|
||||
def read(self):
|
||||
# return the frame most recently read
|
||||
return (self.grabbed, self.frame)
|
||||
|
||||
def stop(self):
|
||||
# indicate that the thread should be stopped
|
||||
self.stopped = True
|
||||
|
||||
# TODO path checking
|
||||
def writeFrame(self, filepath):
|
||||
CV.imwrite(filepath, self.frame)
|
||||
print('saved camview image to: {}'.format(filepath))
|
||||
|
||||
def list_ports(self,api):
|
||||
"""
|
||||
Test the ports and returns a tuple with the available ports and the ones that are working.
|
||||
"""
|
||||
non_working_ports = []
|
||||
dev_port = 0
|
||||
working_ports = []
|
||||
available_ports = []
|
||||
while len(non_working_ports) < 6: # if there are more than 5 non working ports stop the testing.
|
||||
camera = CV.VideoCapture(dev_port, api)
|
||||
if not camera.isOpened():
|
||||
non_working_ports.append(dev_port)
|
||||
LOG.debug("Port %s is not working." %dev_port)
|
||||
else:
|
||||
is_reading, img = camera.read()
|
||||
w = camera.get(3)
|
||||
h = camera.get(4)
|
||||
if is_reading:
|
||||
LOG.debug("Port %s is working and reads images (%s x %s)" %(dev_port,w,h))
|
||||
working_ports.append(dev_port)
|
||||
else:
|
||||
LOG.debug("Port %s for camera ( %s x %s) is present but does not read." %(dev_port,w,h))
|
||||
available_ports.append(dev_port)
|
||||
dev_port +=1
|
||||
camera.release()
|
||||
return available_ports,working_ports,non_working_ports
|
||||
|
||||
class CamAngle(CamView):
|
||||
def __init__(self, parent=None):
|
||||
super(CamAngle, self).__init__(parent)
|
||||
|
||||
def mouseDoubleClickEvent(self, event):
|
||||
if event.button() & QtCore.Qt.LeftButton:
|
||||
# zoom
|
||||
self.scale = 1
|
||||
elif event.button() & QtCore.Qt.MiddleButton:
|
||||
self.diameter = 40
|
||||
|
||||
def wheelEvent(self, event):
|
||||
mouse_state = QtWidgets.QApplication.mouseButtons()
|
||||
size = self.size()
|
||||
w = size.width()
|
||||
if event.angleDelta().y() < 0:
|
||||
if mouse_state == QtCore.Qt.NoButton:
|
||||
self.diameter -= 2
|
||||
if mouse_state == QtCore.Qt.LeftButton:
|
||||
self.scale -= .1
|
||||
else:
|
||||
if mouse_state == QtCore.Qt.NoButton:
|
||||
self.diameter += 2
|
||||
if mouse_state == QtCore.Qt.LeftButton:
|
||||
self.scale += .1
|
||||
if self.diameter < 2: self.diameter = 2
|
||||
if self.diameter > w: self.diameter = w
|
||||
if self.scale < 1: self.scale = 1
|
||||
if self.scale > 5: self.scale = 5
|
||||
|
||||
def drawText(self, event, qp):
|
||||
size = self.size()
|
||||
w = size.width()
|
||||
h = size.height()
|
||||
qp.setPen(self.text_color)
|
||||
qp.setFont(self.font)
|
||||
if self.pix:
|
||||
angle = 0.0 if self.rotation == 0 else 360 - self.rotation
|
||||
qp.drawText(self.rect(), QtCore.Qt.AlignTop, '{:0.3f}{}'.format(angle, self.degree))
|
||||
else:
|
||||
qp.drawText(self.rect(), QtCore.Qt.AlignCenter, self.text)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
import sys
|
||||
from qtpy.QtWidgets import (QLabel, QSlider, QDial,QWidget, QVBoxLayout,
|
||||
QHBoxLayout)
|
||||
from qtpy.QtCore import (Qt)
|
||||
|
||||
def hDialMoved():
|
||||
print("Dial value = %i" % (hdial.value()))
|
||||
capture.scaleX = hdial.value()/100
|
||||
|
||||
def vDialMoved():
|
||||
print("VDial value = %i" % (vdial.value()))
|
||||
capture.scaleY = vdial.value()/100
|
||||
|
||||
def setScale():
|
||||
print("slider value = %i" % (slider.value()))
|
||||
capture.scale = slider.value()/100
|
||||
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
w = QWidget()
|
||||
w.setGeometry(100, 100, 400, 200)
|
||||
w.setWindowTitle('Bar widget')
|
||||
layout = QVBoxLayout(w)
|
||||
hlyt = QHBoxLayout()
|
||||
|
||||
capture = CamAngle()
|
||||
|
||||
slider = QSlider(Qt.Horizontal)
|
||||
slider.setMinimum(100)
|
||||
slider.setMaximum(400)
|
||||
slider.setSingleStep(10)
|
||||
slider.setPageStep(100)
|
||||
slider.valueChanged.connect(setScale)
|
||||
slider.setValue(100)
|
||||
|
||||
hdial = QDial()
|
||||
hdial.setMinimum(100)
|
||||
hdial.setMaximum(200)
|
||||
hdial.setValue(100)
|
||||
hdial.valueChanged.connect(hDialMoved)
|
||||
|
||||
vdial = QDial()
|
||||
vdial.setMinimum(100)
|
||||
vdial.setMaximum(200)
|
||||
vdial.setValue(100)
|
||||
vdial.valueChanged.connect(vDialMoved)
|
||||
|
||||
hlyt.addWidget(capture)
|
||||
hlyt.addWidget(hdial)
|
||||
hlyt.addWidget(vdial)
|
||||
|
||||
layout.addLayout(hlyt)
|
||||
layout.addWidget(slider)
|
||||
|
||||
def jump():
|
||||
capture.nextFrameSlot(None)
|
||||
|
||||
timer = QtCore.QTimer()
|
||||
timer.timeout.connect(jump)
|
||||
timer.start(100)
|
||||
|
||||
w.show()
|
||||
sys.exit(app.exec())
|
||||
Reference in New Issue
Block a user