class CameraInterface(wx.Frame, Module): def __init__(self, *args, **kwds): # begin wxGlade: CameraInterface.__init__ kwds["style"] = kwds.get( "style", 0 ) | wx.DEFAULT_FRAME_STYLE | wx.FRAME_FLOAT_ON_PARENT | wx.TAB_TRAVERSAL wx.Frame.__init__(self, *args, **kwds) Module.__init__(self) self.SetSize((608, 549)) self.CameraInterface_menubar = wx.MenuBar() wxglade_tmp_menu = wx.Menu() item = wxglade_tmp_menu.Append(wx.ID_ANY, _("Reset Perspective"), "") self.Bind(wx.EVT_MENU, self.reset_perspective, id=item.GetId()) item = wxglade_tmp_menu.Append(wx.ID_ANY, _("Reset Fisheye"), "") self.Bind(wx.EVT_MENU, self.reset_fisheye, id=item.GetId()) wxglade_tmp_menu.AppendSeparator() item = wxglade_tmp_menu.Append(wx.ID_ANY, _("Set IP Camera 1"), "", wx.ITEM_RADIO) self.camera_ip_menu1 = item self.Bind(wx.EVT_MENU, lambda e: self.swap_camera(-1), id=item.GetId()) item = wxglade_tmp_menu.Append(wx.ID_ANY, _("Set IP Camera 2"), "", wx.ITEM_RADIO) self.camera_ip_menu2 = item self.Bind(wx.EVT_MENU, lambda e: self.swap_camera(-2), id=item.GetId()) item = wxglade_tmp_menu.Append(wx.ID_ANY, _("Set IP Camera 3"), "", wx.ITEM_RADIO) self.camera_ip_menu3 = item self.Bind(wx.EVT_MENU, lambda e: self.swap_camera(-3), id=item.GetId()) item = wxglade_tmp_menu.Append(wx.ID_ANY, _("Set Camera 0"), "", wx.ITEM_RADIO) self.camera_0_menu = item self.Bind(wx.EVT_MENU, lambda e: self.swap_camera(0), id=item.GetId()) item = wxglade_tmp_menu.Append(wx.ID_ANY, _("Set Camera 1"), "", wx.ITEM_RADIO) self.camera_1_menu = item self.Bind(wx.EVT_MENU, lambda e: self.swap_camera(1), id=item.GetId()) item = wxglade_tmp_menu.Append(wx.ID_ANY, _("Set Camera 2"), "", wx.ITEM_RADIO) self.camera_2_menu = item self.Bind(wx.EVT_MENU, lambda e: self.swap_camera(2), id=item.GetId()) item = wxglade_tmp_menu.Append(wx.ID_ANY, _("Set Camera 3"), "", wx.ITEM_RADIO) self.camera_3_menu = item self.Bind(wx.EVT_MENU, lambda e: self.swap_camera(3), id=item.GetId()) item = wxglade_tmp_menu.Append(wx.ID_ANY, _("Set Camera 4"), "", wx.ITEM_RADIO) self.camera_4_menu = item self.Bind(wx.EVT_MENU, lambda e: self.swap_camera(4), id=item.GetId()) self.CameraInterface_menubar.Append(wxglade_tmp_menu, _("Camera")) self.SetMenuBar(self.CameraInterface_menubar) # Menu Bar self.button_update = wx.BitmapButton(self, wx.ID_ANY, icons8_camera_50.GetBitmap()) self.button_export = wx.BitmapButton( self, wx.ID_ANY, icons8_picture_in_picture_alternative_50.GetBitmap()) self.check_fisheye = wx.CheckBox(self, wx.ID_ANY, _("Correct Fisheye")) self.check_perspective = wx.CheckBox(self, wx.ID_ANY, _("Correct Perspective")) self.slider_fps = wx.Slider(self, wx.ID_ANY, 1, 0, 24, style=wx.SL_AUTOTICKS | wx.SL_HORIZONTAL | wx.SL_LABELS) self.button_detect = wx.BitmapButton(self, wx.ID_ANY, icons8_detective_50.GetBitmap()) self.display_camera = wx.Panel(self, wx.ID_ANY) self.__set_properties() self.__do_layout() self.capture = None self.image_width = -1 self.image_height = -1 self._Buffer = None self.frame_bitmap = None self.fisheye_k = None self.fisheye_d = None # Used during calibration. self.objpoints = [] # 3d point in real world space self.imgpoints = [] # 2d points in image plane. # Perspective Points self.perspective = None self.previous_window_position = None self.previous_scene_position = None self.corner_drag = None self.matrix = Matrix() self.Bind(wx.EVT_BUTTON, self.on_button_update, self.button_update) self.Bind(wx.EVT_BUTTON, self.on_button_export, self.button_export) self.Bind(wx.EVT_CHECKBOX, self.on_check_fisheye, self.check_fisheye) self.Bind(wx.EVT_CHECKBOX, self.on_check_perspective, self.check_perspective) self.Bind(wx.EVT_SLIDER, self.on_slider_fps, self.slider_fps) self.Bind(wx.EVT_BUTTON, self.on_button_detect, self.button_detect) self.SetDoubleBuffered(True) # end wxGlade self.display_camera.Bind(wx.EVT_PAINT, self.on_paint) self.display_camera.Bind(wx.EVT_ERASE_BACKGROUND, self.on_erase) self.display_camera.Bind(wx.EVT_MOTION, self.on_mouse_move) self.display_camera.Bind(wx.EVT_MOUSEWHEEL, self.on_mousewheel) self.display_camera.Bind(wx.EVT_MIDDLE_UP, self.on_mouse_middle_up) self.display_camera.Bind(wx.EVT_MIDDLE_DOWN, self.on_mouse_middle_down) self.display_camera.Bind(wx.EVT_LEFT_DOWN, self.on_mouse_left_down) self.display_camera.Bind(wx.EVT_LEFT_UP, self.on_mouse_left_up) self.display_camera.Bind(wx.EVT_ENTER_WINDOW, lambda event: self.display_camera.SetFocus( )) # Focus follows mouse. self.Bind(wx.EVT_CLOSE, self.on_close, self) self.on_size(None) self.Bind(wx.EVT_SIZE, self.on_size, self) self.camera_lock = threading.Lock() self.process = self.fetch_image self.camera_job = None self.fetch_job = None def __do_layout(self): sizer_1 = wx.BoxSizer(wx.VERTICAL) sizer_2 = wx.BoxSizer(wx.HORIZONTAL) sizer_3 = wx.BoxSizer(wx.VERTICAL) sizer_2.Add(self.button_update, 0, 0, 0) sizer_2.Add(self.button_export, 0, 0, 0) sizer_3.Add(self.check_fisheye, 0, 0, 0) sizer_3.Add(self.check_perspective, 0, 0, 0) sizer_2.Add(sizer_3, 1, wx.EXPAND, 0) sizer_2.Add(self.slider_fps, 1, wx.EXPAND, 0) sizer_2.Add(self.button_detect, 0, 0, 0) sizer_1.Add(sizer_2, 1, wx.EXPAND, 0) sizer_1.Add(self.display_camera, 10, wx.EXPAND, 0) self.SetSizer(sizer_1) self.Layout() def __set_properties(self): _icon = wx.NullIcon _icon.CopyFromBitmap(icons8_camera_50.GetBitmap()) self.SetIcon(_icon) # begin wxGlade: CameraInterface.__set_properties self.SetTitle("CameraInterface") self.button_update.SetToolTip(_("Update Image")) self.button_update.SetSize(self.button_update.GetBestSize()) self.button_export.SetToolTip(_("Export Snapsnot")) self.button_export.SetSize(self.button_export.GetBestSize()) self.button_detect.SetToolTip(_("Detect Distortions/Calibration")) self.button_detect.SetSize(self.button_detect.GetBestSize()) # end wxGlade def on_close(self, event): if self.state == 5: event.Veto() else: self.state = 5 self.device.close('window', self.name) event.Skip() # Call destroy as regular. def initialize(self, channel=None): self.device.close('window', self.name) self.Show() self.device.setting(int, 'draw_mode', 0) self.device.setting(int, 'camera_index', 0) self.device.setting(int, 'camera_fps', 1) self.device.setting(bool, 'mouse_zoom_invert', False) self.device.setting(bool, 'camera_correction_fisheye', False) self.device.setting(bool, 'camera_correction_perspective', False) self.device.setting(str, 'fisheye', '') self.device.setting(str, 'perspective', '') self.device.setting(str, 'camera_uri1', '') self.device.setting(str, 'camera_uri2', '') self.device.setting(str, 'camera_uri3', '') self.check_fisheye.SetValue(self.device.camera_correction_fisheye) self.check_perspective.SetValue( self.device.camera_correction_perspective) if self.device.fisheye is not None and len(self.device.fisheye) != 0: self.fisheye_k, self.fisheye_d = eval(self.device.fisheye) if self.device.perspective is not None and len( self.device.perspective) != 0: self.perspective = eval(self.device.perspective) self.slider_fps.SetValue(self.device.camera_fps) if self.device.camera_index == -3: self.camera_ip_menu3.Check(True) elif self.device.camera_index == -2: self.camera_ip_menu2.Check(True) elif self.device.camera_index == -1: self.camera_ip_menu1.Check(True) elif self.device.camera_index == 0: self.camera_0_menu.Check(True) elif self.device.camera_index == 1: self.camera_1_menu.Check(True) elif self.device.camera_index == 2: self.camera_2_menu.Check(True) elif self.device.camera_index == 3: self.camera_3_menu.Check(True) elif self.device.camera_index == 4: self.camera_4_menu.Check(True) if self.camera_job is not None: self.camera_job.cancel() self.camera_job = self.device.add_job(self.init_camera, times=1, interval=0.1) self.device.listen('camera_frame', self.on_camera_frame_update) self.device.listen('camera_frame_raw', self.on_camera_frame_raw) def finalize(self, channel=None): self.device.unlisten('camera_frame_raw', self.on_camera_frame_raw) self.device.unlisten('camera_frame', self.on_camera_frame_update) self.device.signal('camera_frame_raw', None) self.unschedule() self.camera_lock.acquire() self.close_camera() self.camera_lock.release() if self.camera_job is not None: self.camera_job.cancel() if self.fetch_job is not None: self.fetch_job.cancel() try: self.Close() except RuntimeError: pass def shutdown(self, channel=None): try: self.Close() except RuntimeError: pass def on_size(self, event): self.Layout() width, height = self.ClientSize if width <= 0: width = 1 if height <= 0: height = 1 self._Buffer = wx.Bitmap(width, height) self.update_in_gui_thread() def on_camera_frame_update(self, frame): self.on_camera_frame(frame) self.update_in_gui_thread() def on_camera_frame(self, frame): if frame is None: return bed_width = self.device.bed_width * 2 bed_height = self.device.bed_height * 2 self.image_height, self.image_width = frame.shape[:2] self.frame_bitmap = wx.Bitmap.FromBuffer(self.image_width, self.image_height, frame) if self.device.camera_correction_perspective: if bed_width != self.image_width or bed_height != self.image_height: self.image_width = bed_width self.image_height = bed_height self.display_camera.SetSize( (self.image_width, self.image_height)) def on_camera_frame_raw(self, frame): if frame is None: return try: import cv2 import numpy as np except: dlg = wx.MessageDialog(None, _("Could not import Camera requirements"), _("Imports Failed"), wx.OK) dlg.ShowModal() dlg.Destroy() CHECKERBOARD = (6, 9) subpix_criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.1) calibration_flags = cv2.fisheye.CALIB_RECOMPUTE_EXTRINSIC + cv2.fisheye.CALIB_CHECK_COND + cv2.fisheye.CALIB_FIX_SKEW objp = np.zeros((1, CHECKERBOARD[0] * CHECKERBOARD[1], 3), np.float32) objp[0, :, :2] = np.mgrid[0:CHECKERBOARD[0], 0:CHECKERBOARD[1]].T.reshape(-1, 2) img = frame gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Find the chess board corners ret, corners = cv2.findChessboardCorners( gray, CHECKERBOARD, cv2.CALIB_CB_ADAPTIVE_THRESH + cv2.CALIB_CB_FAST_CHECK + cv2.CALIB_CB_NORMALIZE_IMAGE) # If found, add object points, image points (after refining them) if ret: self.objpoints.append(objp) cv2.cornerSubPix(gray, corners, (3, 3), (-1, -1), subpix_criteria) self.imgpoints.append(corners) else: dlg = wx.MessageDialog(None, _("Checkerboard 6x9 pattern not found."), _("Pattern not found."), wx.OK) dlg.ShowModal() dlg.Destroy() return N_OK = len(self.objpoints) K = np.zeros((3, 3)) D = np.zeros((4, 1)) rvecs = [np.zeros((1, 1, 3), dtype=np.float64) for i in range(N_OK)] tvecs = [np.zeros((1, 1, 3), dtype=np.float64) for i in range(N_OK)] try: rms, a, b, c, d = \ cv2.fisheye.calibrate( self.objpoints, self.imgpoints, gray.shape[::-1], K, D, rvecs, tvecs, calibration_flags, (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 1e-6) ) except cv2.error: # Ill conditioned matrix for input values. self.objpoints = self.objpoints[:-1] # Deleting the last entry. self.imgpoints = self.imgpoints[:-1] dlg = wx.MessageDialog(None, _("Ill-conditioned Matrix. Keep trying."), _("Matrix."), wx.OK) dlg.ShowModal() dlg.Destroy() return dlg = wx.MessageDialog( None, _("Success. %d images so far." % len(self.objpoints)), _("Image Captured"), wx.OK | wx.ICON_INFORMATION) dlg.ShowModal() dlg.Destroy() self.device.fisheye = repr([K.tolist(), D.tolist()]) self.fisheye_k = K.tolist() self.fisheye_d = D.tolist() def swap_camera(self, camera_index=0): self.camera_lock.acquire() self.device.camera_index = camera_index self.close_camera() if self.camera_job is not None: self.camera_job.cancel() if camera_index < 0: self.camera_uri_request(abs(camera_index)) uri = self.get_camera_uri() if uri is None or len(uri) == 0: self.camera_lock.release() return self.camera_job = self.device.add_job(self.init_camera, times=1, interval=0.1) self.camera_lock.release() def close_camera(self): self.unschedule() if self.capture is not None: self.capture.release() self.capture = None def camera_error_requirement(self): try: for attr in dir(self): value = getattr(self, attr) if isinstance(value, wx.Control): value.Enable(False) dlg = wx.MessageDialog( None, _("If using a precompiled binary, this was requirement was not included.\nIf using pure Python, add it with: pip install opencv-python-headless" ), _("Interface Requires OpenCV."), wx.OK | wx.ICON_ERROR) dlg.ShowModal() dlg.Destroy() except RuntimeError: pass def camera_error_webcam(self): try: for attr in dir(self): value = getattr(self, attr) if isinstance(value, wx.Control): value.Enable(False) dlg = wx.MessageDialog(None, _("No Webcam found."), _("Error"), wx.OK | wx.ICON_ERROR) dlg.ShowModal() dlg.Destroy() except RuntimeError: pass def camera_uri_request(self, index=None): if index is None: index = abs(self.device.camera_index) try: dlg = wx.TextEntryDialog( self, _("Enter the HTTP or RTSP uri for the webcam #%d") % index, _("Webcam URI Update"), '') dlg.SetValue(self.get_camera_uri()) modal = dlg.ShowModal() if modal == wx.ID_OK: self.set_camera_uri(str(dlg.GetValue())) wx.CallAfter(self.init_camera) else: wx.CallAfter(self.camera_error_webcam) dlg.Destroy() except RuntimeError: pass def camera_success(self): try: for attr in dir(self): value = getattr(self, attr) if isinstance(value, wx.Control): value.Enable(True) except RuntimeError: pass def set_camera_uri(self, uri): return setattr(self.device, "camera_uri%d" % abs(self.device.camera_index), uri) def get_camera_uri(self): index = self.device.camera_index if index >= 0: return index return getattr(self.device, "camera_uri%d" % abs(index)) def init_camera(self): if self.device is None: return if self.capture is not None: self.capture = None try: import cv2 except ImportError: wx.CallAfter(self.camera_error_requirement) return if self.device.camera_index >= 0: self.capture = cv2.VideoCapture(self.device.camera_index) else: uri = self.get_camera_uri() if uri is None or len(uri) == 0: return else: self.capture = cv2.VideoCapture(self.get_camera_uri()) wx.CallAfter(self.camera_success) try: self.interval = 1.0 / self.device.camera_fps except ZeroDivisionError: self.interval = 5 except AttributeError: return self.schedule() def reset_perspective(self, event): self.perspective = None self.device.perspective = '' def reset_fisheye(self, event): self.fisheye_k = None self.fisheye_d = None self.device.fisheye = '' def on_erase(self, event): pass def on_paint(self, event): try: wx.BufferedPaintDC(self.display_camera, self._Buffer) except RuntimeError: pass def on_update_buffer(self, event=None): if self.frame_bitmap is None: return # Need the bitmap to refresh. if self.device is None: return # No window to draw in. dm = self.device.draw_mode dc = wx.MemoryDC() dc.SelectObject(self._Buffer) dc.Clear() w, h = dc.Size if dm & DRAW_MODE_FLIPXY != 0: dc.SetUserScale(-1, -1) dc.SetLogicalOrigin(w, h) dc.SetBackground(wx.WHITE_BRUSH) gc = wx.GraphicsContext.Create(dc) gc.SetTransform( wx.GraphicsContext.CreateMatrix(gc, ZMatrix(self.matrix))) gc.PushState() gc.DrawBitmap(self.frame_bitmap, 0, 0, self.image_width, self.image_height) if not self.device.camera_correction_perspective: if self.perspective is None: self.perspective = [0, 0], \ [self.image_width, 0], \ [self.image_width, self.image_height], \ [0, self.image_height] gc.SetPen(wx.BLACK_DASHED_PEN) gc.StrokeLines(self.perspective) gc.StrokeLine(self.perspective[0][0], self.perspective[0][1], self.perspective[3][0], self.perspective[3][1]) gc.SetPen(wx.BLUE_PEN) for p in self.perspective: half = CORNER_SIZE / 2 gc.StrokeLine(p[0] - half, p[1], p[0] + half, p[1]) gc.StrokeLine(p[0], p[1] - half, p[0], p[1] + half) gc.DrawEllipse(p[0] - half, p[1] - half, CORNER_SIZE, CORNER_SIZE) gc.PopState() if dm & DRAW_MODE_INVERT != 0: dc.Blit(0, 0, w, h, dc, 0, 0, wx.SRC_INVERT) gc.Destroy() del dc def convert_scene_to_window(self, position): point = self.matrix.point_in_matrix_space(position) return point[0], point[1] def convert_window_to_scene(self, position): point = self.matrix.point_in_inverse_space(position) return point[0], point[1] def on_mouse_move(self, event): if not event.Dragging(): return else: self.SetCursor(wx.Cursor(wx.CURSOR_HAND)) if self.previous_window_position is None: return pos = event.GetPosition() window_position = pos.x, pos.y scene_position = self.convert_window_to_scene( [window_position[0], window_position[1]]) sdx = (scene_position[0] - self.previous_scene_position[0]) sdy = (scene_position[1] - self.previous_scene_position[1]) wdx = (window_position[0] - self.previous_window_position[0]) wdy = (window_position[1] - self.previous_window_position[1]) if self.corner_drag is None: self.scene_post_pan(wdx, wdy) else: self.perspective[self.corner_drag][0] += sdx self.perspective[self.corner_drag][1] += sdy self.device.perspective = repr(self.perspective) self.previous_window_position = window_position self.previous_scene_position = scene_position def on_mousewheel(self, event): rotation = event.GetWheelRotation() mouse = event.GetPosition() if self.device.device_root.mouse_zoom_invert: rotation = -rotation if rotation > 1: self.scene_post_scale(1.1, 1.1, mouse[0], mouse[1]) elif rotation < -1: self.scene_post_scale(0.9, 0.9, mouse[0], mouse[1]) def on_mouse_left_down(self, event): self.previous_window_position = event.GetPosition() self.previous_scene_position = self.convert_window_to_scene( self.previous_window_position) self.corner_drag = None if self.perspective is not None: for i, p in enumerate(self.perspective): half = CORNER_SIZE / 2 if Point.distance(self.previous_scene_position, p) < half: self.corner_drag = i break def on_mouse_left_up(self, event): self.SetCursor(wx.Cursor(wx.CURSOR_ARROW)) self.previous_window_position = None self.previous_scene_position = None self.corner_drag = None def on_mouse_middle_down(self, event): self.SetCursor(wx.Cursor(wx.CURSOR_HAND)) self.previous_window_position = event.GetPosition() self.previous_scene_position = self.convert_window_to_scene( self.previous_window_position) def on_mouse_middle_up(self, event): self.SetCursor(wx.Cursor(wx.CURSOR_ARROW)) self.previous_window_position = None self.previous_scene_position = None def scene_post_pan(self, px, py): self.matrix.post_translate(px, py) self.on_update_buffer() def scene_post_scale(self, sx, sy=None, ax=0, ay=0): self.matrix.post_scale(sx, sy, ax, ay) self.on_update_buffer() def fetch_image(self, raw=False): if self.device is None or self.capture is None: return try: import cv2 except ImportError: return if self.capture is None: return self.camera_lock.acquire() if self.device is None: self.camera_lock.release() return for i in range(10): ret = self.capture.grab() if not ret: if i >= 9: self.capture = None wx.CallAfter(self.camera_error_webcam) self.camera_lock.release() return time.sleep(0.05) else: break for i in range(10): ret, frame = self.capture.retrieve() if not ret or frame is None: if i >= 9: wx.CallAfter(self.camera_error_webcam) self.capture = None self.camera_lock.release() return time.sleep(0.05) else: break if not raw and \ self.fisheye_k is not None and \ self.fisheye_d is not None and \ self.device is not None and \ self.device.camera_correction_fisheye: # Unfisheye the drawing import numpy as np K = np.array(self.fisheye_k) D = np.array(self.fisheye_d) DIM = frame.shape[:2][::-1] map1, map2 = cv2.fisheye.initUndistortRectifyMap( K, D, np.eye(3), K, DIM, cv2.CV_16SC2) frame = cv2.remap(frame, map1, map2, interpolation=cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT) if not raw and self.device is not None and self.device.camera_correction_perspective: bed_width = self.device.bed_width * 2 bed_height = self.device.bed_height * 2 width, height = frame.shape[:2][::-1] import numpy as np if self.perspective is None: rect = np.array([[0, 0], [width - 1, 0], [width - 1, height - 1], [0, height - 1]], dtype="float32") else: rect = np.array(self.perspective, dtype="float32") dst = np.array( [[0, 0], [bed_width - 1, 0], [bed_width - 1, bed_height - 1], [0, bed_height - 1]], dtype="float32") M = cv2.getPerspectiveTransform(rect, dst) frame = cv2.warpPerspective(frame, M, (bed_width, bed_height)) frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) if raw: self.device.signal('camera_frame_raw', frame) else: self.device.signal('camera_frame', frame) self.camera_lock.release() return frame def update_in_gui_thread(self): self.on_update_buffer() try: self.Refresh(True) self.Update() except RuntimeError: pass def on_check_perspective(self, event): self.device.camera_correction_perspective = self.check_perspective.GetValue( ) def on_check_fisheye(self, event): self.device.camera_correction_fisheye = self.check_fisheye.GetValue() def on_button_update(self, event): # wxGlade: CameraInterface.<event_handler> frame = self.device.last_signal('camera_frame') if frame is not None: frame = frame[0] buffer = wx.Bitmap.FromBuffer(self.image_width, self.image_height, frame) self.device.signal('background', buffer) def on_button_export(self, event): # wxGlade: CameraInterface.<event_handler> frame = self.device.last_signal('camera_frame') if frame is not None: elements = self.device.device_root.elements frame = frame[0] from PIL import Image img = Image.fromarray(frame) obj = SVGImage() obj.image = img obj.image_width = self.image_width obj.image_height = self.image_height elements.add_elem(obj) def on_slider_fps(self, event): # wxGlade: CameraInterface.<event_handler> fps = self.slider_fps.GetValue() if fps == 0: tick = 5 else: tick = 1.0 / fps self.device.camera_fps = fps self.interval = tick def on_button_detect(self, event): # wxGlade: CameraInterface.<event_handler> if self.fetch_job is not None: self.fetch_job.cancel() self.fetch_job = self.device.add_job(self.fetch_image, args=True, times=1, interval=0)
class Widget(list): def __init__(self, scene, left=None, top=None, right=None, bottom=None, all=False): list.__init__(self) self.matrix = Matrix() self.scene = scene self.parent = None self.properties = ORIENTATION_RELATIVE if all: # contains all points self.left = -float('inf') self.top = -float('inf') self.right = float('inf') self.bottom = float('inf') else: # contains no points self.left = float('inf') self.top = float('inf') self.right = -float('inf') self.bottom = -float('inf') if left is not None: self.left = left if right is not None: self.right = right if top is not None: self.top = top if bottom is not None: self.bottom = bottom def __str__(self): return 'Widget(%f, %f, %f, %f)' % (self.left, self.top, self.right, self.bottom) def __repr__(self): return '%s(%f, %f, %f, %f)' % (type(self).__name__, self.left, self.top, self.right, self.bottom) def hit(self): return HITCHAIN_DELEGATE def draw(self, gc): # Concat if this is a thing. m = self.matrix gc.PushState() gc.ConcatTransform(wx.GraphicsContext.CreateMatrix(gc, ZMatrix(m))) self.process_draw(gc) for i in range(len(self) - 1, -1, -1): widget = self[i] widget.draw(gc) gc.PopState() def process_draw(self, gc): pass def contains(self, x, y=None): if y is None: y = x.y x = x.x return self.left <= x <= self.right and \ self.top <= y <= self.bottom def event(self, window_pos=None, space_pos=None, event_type=None): return RESPONSE_CHAIN def notify_added_to_parent(self, parent): pass def notify_added_child(self, child): pass def notify_removed_from_parent(self, parent): pass def notify_removed_child(self, child): pass def notify_moved_child(self, child): pass def add_widget(self, index=-1, widget=None, properties=0): if len(self) == 0: last = None else: last = self[-1] if 0 <= index < len(self): self.insert(index, widget) else: self.append(widget) widget.parent = self self.layout_by_orientation(widget, last, properties) self.notify_added_to_parent(self) self.notify_added_child(widget) def translate(self, dx, dy): if dx == 0 and dy == 0: return if dx == float('nan'): return if dy == float('nan'): return if abs(dx) == float('inf'): return if abs(dy) == float('inf'): return self.translate_loop(dx, dy) def translate_loop(self, dx, dy): if self.properties & ORIENTATION_ABSOLUTE != 0: return # Do not translate absolute oriented widgets. self.translate_self(dx, dy) for w in self: w.translate_loop(dx, dy) def translate_self(self, dx, dy): self.left += dx self.right += dx self.top += dy self.bottom += dy if self.parent is not None: self.notify_moved_child(self) def union_children_bounds(self, bounds=None): if bounds is None: bounds = [self.left, self.top, self.right, self.bottom] else: if bounds[0] > self.left: bounds[0] = self.left if bounds[1] > self.top: bounds[1] = self.top if bounds[2] < self.right: bounds[2] = self.left if bounds[3] < self.bottom: bounds[3] = self.bottom for w in self: w.union_children_bounds(bounds) return bounds @property def height(self): return self.bottom - self.top @property def width(self): return self.right - self.left def layout_by_orientation(self, widget, last, properties): if properties & ORIENTATION_ABSOLUTE != 0: return if properties & ORIENTATION_NO_BUFFER != 0: buffer = 0 else: buffer = BUFFER if (properties & ORIENTATION_MODE_MASK) == ORIENTATION_RELATIVE: widget.translate(self.left, self.top) return elif last is None: # orientation = origin widget.translate(self.left - widget.left, self.top - widget.top) elif (properties & ORIENTATION_GRID) != 0: dim = properties & ORIENTATION_DIM_MASK if (properties & ORIENTATION_VERTICAL) != 0: if dim == 0: # Vertical if self.height >= last.bottom - self.top + widget.height: # add to line widget.translate(last.left - widget.left, last.bottom - widget.top) else: # line return widget.translate(last.right - widget.left + buffer, self.top - widget.top) else: if dim == 0: # Horizontal if self.width >= last.right - self.left + widget.width: # add to line widget.translate(last.right - widget.left + buffer, last.top - widget.top) else: # line return widget.translate(self.left - widget.left, last.bottom - widget.top + buffer) elif (properties & ORIENTATION_HORIZONTAL) != 0: widget.translate(last.right - widget.left + buffer, last.top - widget.top) elif (properties & ORIENTATION_VERTICAL) != 0: widget.translate(last.left - widget.left, last.bottom - widget.top + buffer) if properties & ORIENTATION_CENTERED: self.center_children() def center_children(self): child_bounds = self.union_children_bounds() dx = self.left - (child_bounds[0] + child_bounds[2]) / 2.0 dy = self.top - (child_bounds[1] + child_bounds[3]) / 2.0 if dx != 0 and dy != 0: for w in self: w.translate_loop(dx, dy) def center_widget(self, x, y=None): if y is None: y = x.y x = x.x child_bounds = self.union_children_bounds() cx = (child_bounds[0] + child_bounds[2]) / 2.0 cy = (child_bounds[1] + child_bounds[3]) / 2.0 self.translate(x - cx, y - cy) def set_position(self, x, y=None): if y is None: y = x.y x = x.x dx = x - self.left dy = y - self.top self.translate(dx, dy) def remove_all_widgets(self): for w in self: if w is None: continue w.parent = None w.notify_removed_from_parent(self) self.notify_removed_child(w) self.clear() try: self.scene.notify_tree_changed() except AttributeError: pass def remove_widget(self, widget=None): if widget is None: return if isinstance(widget, Widget): list.remove(widget) elif isinstance(widget, int): index = widget widget = self[index] list.remove(index) widget.parent = None widget.notify_removed_from_parent(self) self.notify_removed_child(widget) try: self.scene.notify_tree_changed() except AttributeError: pass def set_widget(self, index, widget): w = self[index] self[index] = widget widget.parent = self widget.notify_added_to_parent(self) self.notify_removed_child(w) try: self.scene.notify_tree_changed() except AttributeError: pass def on_matrix_change(self): pass def scene_matrix_reset(self): self.matrix.reset() self.on_matrix_change() def scene_post_scale(self, sx, sy=None, ax=0, ay=0): self.matrix.post_scale(sx, sy, ax, ay) self.on_matrix_change() def scene_post_pan(self, px, py): self.matrix.post_translate(px, py) self.on_matrix_change() def scene_post_rotate(self, angle, rx=0, ry=0): self.matrix.post_rotate(angle, rx, ry) self.on_matrix_change() def scene_pre_scale(self, sx, sy=None, ax=0, ay=0): self.matrix.pre_scale(sx, sy, ax, ay) self.on_matrix_change() def scene_pre_pan(self, px, py): self.matrix.pre_translate(px, py) self.on_matrix_change() def scene_pre_rotate(self, angle, rx=0, ry=0): self.matrix.pre_rotate(angle, rx, ry) self.on_matrix_change() def get_scale_x(self): return self.matrix.value_scale_x() def get_scale_y(self): return self.matrix.value_scale_y() def get_skew_x(self): return self.matrix.value_skew_x() def get_skew_y(self): return self.matrix.value_skew_y() def get_translate_x(self): return self.matrix.value_trans_x() def get_translate_y(self): return self.matrix.value_trans_y()
class CameraInterface(wx.Frame, Module): def __init__(self, parent, *args, **kwds): wx.Frame.__init__(self, parent, -1, "", style=wx.DEFAULT_FRAME_STYLE | wx.FRAME_FLOAT_ON_PARENT | wx.TAB_TRAVERSAL) Module.__init__(self) if args >= 1: self.settings_value = args[0] else: self.settings_value = 0 self.SetSize((600, 600)) self.CameraInterface_menubar = wx.MenuBar() wxglade_tmp_menu = wx.Menu() item = wxglade_tmp_menu.Append(wx.ID_ANY, _("Reset Perspective"), "") self.Bind(wx.EVT_MENU, self.reset_perspective, id=item.GetId()) item = wxglade_tmp_menu.Append(wx.ID_ANY, _("Reset Fisheye"), "") self.Bind(wx.EVT_MENU, self.reset_fisheye, id=item.GetId()) wxglade_tmp_menu.AppendSeparator() item = wxglade_tmp_menu.Append(wx.ID_ANY, _("Set URI"), "") self.Bind(wx.EVT_MENU, lambda e: self.ip_menu_edit(), id=item.GetId()) item = wxglade_tmp_menu.Append(wx.ID_ANY, _("Set IP Camera"), "", wx.ITEM_RADIO) self.ip_camera_menu = item self.Bind(wx.EVT_MENU, lambda e: self.swap_camera(-1), id=item.GetId()) item = wxglade_tmp_menu.Append(wx.ID_ANY, _("Set Camera 0"), "", wx.ITEM_RADIO) self.camera_0_menu = item self.Bind(wx.EVT_MENU, lambda e: self.swap_camera(0), id=item.GetId()) item = wxglade_tmp_menu.Append(wx.ID_ANY, _("Set Camera 1"), "", wx.ITEM_RADIO) self.camera_1_menu = item self.Bind(wx.EVT_MENU, lambda e: self.swap_camera(1), id=item.GetId()) item = wxglade_tmp_menu.Append(wx.ID_ANY, _("Set Camera 2"), "", wx.ITEM_RADIO) self.camera_2_menu = item self.Bind(wx.EVT_MENU, lambda e: self.swap_camera(2), id=item.GetId()) item = wxglade_tmp_menu.Append(wx.ID_ANY, _("Set Camera 3"), "", wx.ITEM_RADIO) self.camera_3_menu = item self.Bind(wx.EVT_MENU, lambda e: self.swap_camera(3), id=item.GetId()) item = wxglade_tmp_menu.Append(wx.ID_ANY, _("Set Camera 4"), "", wx.ITEM_RADIO) self.camera_4_menu = item self.Bind(wx.EVT_MENU, lambda e: self.swap_camera(4), id=item.GetId()) self.CameraInterface_menubar.Append(wxglade_tmp_menu, _("Camera")) self.SetMenuBar(self.CameraInterface_menubar) # Menu Bar self.button_update = wx.BitmapButton(self, wx.ID_ANY, icons8_camera_50.GetBitmap()) self.button_export = wx.BitmapButton( self, wx.ID_ANY, icons8_picture_in_picture_alternative_50.GetBitmap()) self.button_reconnect = wx.BitmapButton( self, wx.ID_ANY, icons8_connected_50.GetBitmap()) self.check_fisheye = wx.CheckBox(self, wx.ID_ANY, _("Correct Fisheye")) self.check_perspective = wx.CheckBox(self, wx.ID_ANY, _("Correct Perspective")) self.slider_fps = wx.Slider(self, wx.ID_ANY, 24, 0, 60, style=wx.SL_AUTOTICKS | wx.SL_HORIZONTAL | wx.SL_LABELS) self.button_detect = wx.BitmapButton(self, wx.ID_ANY, icons8_detective_50.GetBitmap()) self.display_camera = wx.Panel(self, wx.ID_ANY) self.__set_properties() self.__do_layout() self.camera_job = None self.fetch_job = None self.camera_setting = None self.setting = None self.current_frame = None self.last_frame = None self.current_raw = None self.last_raw = None self.capture = None self.image_width = -1 self.image_height = -1 self._Buffer = None self.frame_bitmap = None self.fisheye_k = None self.fisheye_d = None # Used during calibration. self.objpoints = [] # 3d point in real world space self.imgpoints = [] # 2d points in image plane. # Perspective Points self.perspective = None self.previous_window_position = None self.previous_scene_position = None self.corner_drag = None self.matrix = Matrix() self.Bind(wx.EVT_BUTTON, self.on_button_update, self.button_update) self.Bind(wx.EVT_BUTTON, self.on_button_export, self.button_export) self.Bind(wx.EVT_BUTTON, self.on_button_reconnect, self.button_reconnect) self.Bind(wx.EVT_CHECKBOX, self.on_check_fisheye, self.check_fisheye) self.Bind(wx.EVT_CHECKBOX, self.on_check_perspective, self.check_perspective) self.Bind(wx.EVT_SLIDER, self.on_slider_fps, self.slider_fps) self.Bind(wx.EVT_BUTTON, self.on_button_detect, self.button_detect) self.SetDoubleBuffered(True) # end wxGlade self.display_camera.Bind(wx.EVT_PAINT, self.on_paint) self.display_camera.Bind(wx.EVT_ERASE_BACKGROUND, self.on_erase) self.display_camera.Bind(wx.EVT_MOTION, self.on_mouse_move) self.display_camera.Bind(wx.EVT_MOUSEWHEEL, self.on_mousewheel) self.display_camera.Bind(wx.EVT_MIDDLE_UP, self.on_mouse_middle_up) self.display_camera.Bind(wx.EVT_MIDDLE_DOWN, self.on_mouse_middle_down) self.display_camera.Bind(wx.EVT_LEFT_DOWN, self.on_mouse_left_down) self.display_camera.Bind(wx.EVT_LEFT_UP, self.on_mouse_left_up) self.display_camera.Bind(wx.EVT_ENTER_WINDOW, lambda event: self.display_camera.SetFocus( )) # Focus follows mouse. self.Bind(wx.EVT_CLOSE, self.on_close, self) self.on_size(None) self.Bind(wx.EVT_SIZE, self.on_size, self) self.camera_lock = threading.Lock() self.process = self.update_view self.connection_attempts = 0 self.frame_attempts = 0 self.last_frame_index = -1 self.frame_index = 0 self.quit_thread = False def __do_layout(self): sizer_1 = wx.BoxSizer(wx.VERTICAL) sizer_2 = wx.BoxSizer(wx.HORIZONTAL) sizer_3 = wx.BoxSizer(wx.VERTICAL) sizer_2.Add(self.button_update, 0, 0, 0) sizer_2.Add(self.button_export, 0, 0, 0) sizer_2.Add(self.button_reconnect, 0, 0, 0) sizer_3.Add(self.check_fisheye, 0, 0, 0) sizer_3.Add(self.check_perspective, 0, 0, 0) sizer_2.Add(sizer_3, 1, wx.EXPAND, 0) sizer_2.Add(self.slider_fps, 1, wx.EXPAND, 0) sizer_2.Add(self.button_detect, 0, 0, 0) sizer_1.Add(sizer_2, 1, wx.EXPAND, 0) sizer_1.Add(self.display_camera, 10, wx.EXPAND, 0) self.SetSizer(sizer_1) self.Layout() def __set_properties(self): _icon = wx.NullIcon _icon.CopyFromBitmap(icons8_camera_50.GetBitmap()) self.SetIcon(_icon) # begin wxGlade: CameraInterface.__set_properties self.SetTitle("CameraInterface") self.button_update.SetToolTip(_("Update Image")) self.button_update.SetSize(self.button_update.GetBestSize()) self.button_export.SetToolTip(_("Export Snapsnot")) self.button_export.SetSize(self.button_export.GetBestSize()) self.button_reconnect.SetToolTip(_("Reconnect Camera")) self.button_reconnect.SetSize(self.button_reconnect.GetBestSize()) self.button_detect.SetToolTip(_("Detect Distortions/Calibration")) self.button_detect.SetSize(self.button_detect.GetBestSize()) # end wxGlade @staticmethod def sub_register(device): device.register('window', "CameraURI", CameraURI) def on_close(self, event): if self.state == 5: event.Veto() else: self.state = 5 self.device.close('window', self.name) event.Skip() # Call destroy as regular. def initialize(self, channel=None): self.device.close('window', self.name) self.Show() self.device.setting(bool, 'mouse_zoom_invert', False) self.device.setting(int, 'draw_mode', 0) self.device.setting(int, "bed_width", 320) # Default Value self.device.setting(int, "bed_height", 220) # Default Value self.camera_setting = self.device.device_root.derive('camera') self.setting = self.camera_setting.derive(str(self.settings_value)) self.setting.setting(int, 'index', 0) self.setting.setting(int, 'fps', 1) self.setting.setting(bool, 'correction_fisheye', False) self.setting.setting(bool, 'correction_perspective', False) self.setting.setting(str, 'fisheye', '') self.setting.setting(str, 'perspective', '') self.setting.setting(str, 'uri', '0') self.check_fisheye.SetValue(self.setting.correction_fisheye) self.check_perspective.SetValue(self.setting.correction_perspective) if self.setting.fisheye is not None and len(self.setting.fisheye) != 0: self.fisheye_k, self.fisheye_d = eval(self.setting.fisheye) if self.setting.perspective is not None and len( self.setting.perspective) != 0: self.perspective = eval(self.setting.perspective) self.slider_fps.SetValue(self.setting.fps) if self.camera_job is not None: self.camera_job.cancel() self.open_camera(self.setting.index) self.device.listen('camera_uri_changed', self.on_camera_uri_change) self.device.listen('camera_frame_raw', self.on_camera_frame_raw) self.set_camera_checks() def finalize(self, channel=None): self.setting.flush() self.camera_setting.flush() self.device.unlisten('camera_uri_changed', self.on_camera_uri_change) self.device.unlisten('camera_frame_raw', self.on_camera_frame_raw) self.quit_thread = True if self.camera_job is not None: self.camera_job.cancel() if self.fetch_job is not None: self.fetch_job.cancel() try: self.Close() except RuntimeError: pass def shutdown(self, channel=None): try: self.Close() self.quit_thread = True except RuntimeError: pass def on_size(self, event): self.Layout() width, height = self.ClientSize if width <= 0: width = 1 if height <= 0: height = 1 self._Buffer = wx.Bitmap(width, height) self.update_in_gui_thread() def on_camera_uri_change(self, *args): pass def ip_menu_edit(self): """ Select a particular URI. :param uri: :return: """ self.device.using( 'module', 'Console').write('window open CameraURI %s %s\n' % (self.settings_value, self.setting.uri)) def ip_menu_uri_change(self, uri): def function(event=None): self.ip_menu_uri(uri) return function def ip_menu_uri(self, uri): """ Select a particular URI. :param uri: :return: """ self.setting.uri = uri self.swap_camera(-1) def set_camera_checks(self): """ Set the checkmarks based on the index value. :return: """ if self.setting.index == 0: self.camera_0_menu.Check(True) elif self.setting.index == 1: self.camera_1_menu.Check(True) elif self.setting.index == 2: self.camera_2_menu.Check(True) elif self.setting.index == 3: self.camera_3_menu.Check(True) elif self.setting.index == 4: self.camera_4_menu.Check(True) else: self.ip_camera_menu.Check(True) def update_view(self): frame = self.last_frame if frame is None: return print("%d vs %d" % (self.last_frame_index, self.frame_index)) if self.frame_index == self.last_frame_index: return else: self.last_frame_index = self.frame_index bed_width = self.device.bed_width * 2 bed_height = self.device.bed_height * 2 self.image_height, self.image_width = frame.shape[:2] self.frame_bitmap = wx.Bitmap.FromBuffer(self.image_width, self.image_height, frame) if self.setting.correction_perspective: if bed_width != self.image_width or bed_height != self.image_height: self.image_width = bed_width self.image_height = bed_height self.display_camera.SetSize( (self.image_width, self.image_height)) self.update_in_gui_thread() def on_camera_frame_raw(self, *args): """ Raw Camera frame was requested and should be processed. This attempts to perform checkboard detection. :param frame: :return: """ frame = self.last_raw if frame is None: return try: import cv2 import numpy as np except: dlg = wx.MessageDialog(None, _("Could not import Camera requirements"), _("Imports Failed"), wx.OK) dlg.ShowModal() dlg.Destroy() CHECKERBOARD = (6, 9) subpix_criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.1) calibration_flags = cv2.fisheye.CALIB_RECOMPUTE_EXTRINSIC + cv2.fisheye.CALIB_CHECK_COND + cv2.fisheye.CALIB_FIX_SKEW objp = np.zeros((1, CHECKERBOARD[0] * CHECKERBOARD[1], 3), np.float32) objp[0, :, :2] = np.mgrid[0:CHECKERBOARD[0], 0:CHECKERBOARD[1]].T.reshape(-1, 2) img = frame gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Find the chess board corners ret, corners = cv2.findChessboardCorners( gray, CHECKERBOARD, cv2.CALIB_CB_ADAPTIVE_THRESH + cv2.CALIB_CB_FAST_CHECK + cv2.CALIB_CB_NORMALIZE_IMAGE) # If found, add object points, image points (after refining them) if ret: self.objpoints.append(objp) cv2.cornerSubPix(gray, corners, (3, 3), (-1, -1), subpix_criteria) self.imgpoints.append(corners) else: dlg = wx.MessageDialog(None, _("Checkerboard 6x9 pattern not found."), _("Pattern not found."), wx.OK) dlg.ShowModal() dlg.Destroy() return N_OK = len(self.objpoints) K = np.zeros((3, 3)) D = np.zeros((4, 1)) rvecs = [np.zeros((1, 1, 3), dtype=np.float64) for i in range(N_OK)] tvecs = [np.zeros((1, 1, 3), dtype=np.float64) for i in range(N_OK)] try: rms, a, b, c, d = \ cv2.fisheye.calibrate( self.objpoints, self.imgpoints, gray.shape[::-1], K, D, rvecs, tvecs, calibration_flags, (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 1e-6) ) except cv2.error: # Ill conditioned matrix for input values. self.objpoints = self.objpoints[:-1] # Deleting the last entry. self.imgpoints = self.imgpoints[:-1] dlg = wx.MessageDialog(None, _("Ill-conditioned Matrix. Keep trying."), _("Matrix."), wx.OK) dlg.ShowModal() dlg.Destroy() return dlg = wx.MessageDialog( None, _("Success. %d images so far." % len(self.objpoints)), _("Image Captured"), wx.OK | wx.ICON_INFORMATION) dlg.ShowModal() dlg.Destroy() self.setting.fisheye = repr([K.tolist(), D.tolist()]) self.fisheye_k = K.tolist() self.fisheye_d = D.tolist() def swap_camera(self, camera_index=0): """ Disconnect the current connected camera. Connect to the provided camera index. :param camera_index: :return: """ with self.camera_lock: self.close_camera() self.open_camera(camera_index) self.set_camera_checks() def init_camera(self): """ Connect to Camera. :return: """ if self.device is None: return if self.capture is not None: self.capture = None try: import cv2 except ImportError: wx.CallAfter(self.camera_error_requirement) return wx.CallAfter(lambda: self.set_control_enable(True)) try: self.interval = 1.0 / self.setting.fps except ZeroDivisionError: self.interval = 5 except AttributeError: return self.device.threaded(self.threaded_image_fetcher) self.schedule() def open_camera(self, camera_index=0): """ Open Camera device. Prevents opening if already opening. :param camera_index: :return: """ if self.camera_job is not None: self.camera_job.cancel() self.setting.index = camera_index uri = self.get_camera_uri() if uri is not None: self.camera_job = self.device.add_job(self.init_camera, times=1, interval=0.1) def close_camera(self): """ Disconnect from the current camera. :return: """ self.unschedule() if self.capture is not None: self.capture.release() self.capture = None def set_control_enable(self, enable=False): """ Disable/Enable all the major controls within the GUI. :param enable: :return: """ try: for attr in dir(self): value = getattr(self, attr) if isinstance(value, wx.Control): if value is not self.button_reconnect: value.Enable(enable) except RuntimeError: pass def camera_error_requirement(self): """ Message Error that OpenCV is not installed and thus camera cannot run. Disable all Controls. :return: """ try: self.set_control_enable(False) dlg = wx.MessageDialog( None, _("If using a precompiled binary, this was requirement was not included.\nIf using pure Python, add it with: pip install opencv-python-headless" ), _("Interface Requires OpenCV."), wx.OK | wx.ICON_ERROR) dlg.ShowModal() dlg.Destroy() except RuntimeError: pass def camera_error_webcam(self): """ Message error based on failure to connect to webcam. Disable all Controls. :return: """ try: self.set_control_enable(False) dlg = wx.MessageDialog(None, _("No Webcam found."), _("Error"), wx.OK | wx.ICON_ERROR) dlg.ShowModal() dlg.Destroy() except RuntimeError: pass def ip_menu_new(self): """ Requests and adds new IP Connection URI. :return: """ dlg = wx.TextEntryDialog(self, _("Enter the HTTP or RTSP uri"), _("Webcam URI Update"), '') dlg.SetValue(str(self.get_camera_uri())) modal = dlg.ShowModal() if modal == wx.ID_OK: uri = str(dlg.GetValue()) index = len(list(self.camera_setting.keylist())) setattr(self.camera_setting, 'uri' + str(index), uri) self.setting.uri = uri self.ip_menu_uri(uri) self.swap_camera(-1) self.set_camera_checks() def get_camera_uri(self): """ Get the current camera URI. If the URI :return: """ index = self.setting.index if index >= 0: return index uri = self.setting.uri if uri is None or len(uri) == 0: return None try: return int(uri) except ValueError: # URI is not a number. return uri def attempt_recovery(self): try: import cv2 except ImportError: return False if self.quit_thread: return False if self.device is None: return False self.frame_attempts += 1 if self.frame_attempts < 5: return True self.frame_attempts = 0 if self.capture is not None: with self.camera_lock: self.capture.release() self.capture = None uri = self.get_camera_uri() if uri is None: return False self.device.signal("camera_initialize") with self.camera_lock: self.capture = cv2.VideoCapture(uri) if self.capture is None: return False self.connection_attempts += 1 return True def threaded_image_fetcher(self): self.quit_thread = False try: import cv2 except ImportError: return raw = False uri = self.get_camera_uri() if uri is None: return self.device.signal("camera_initialize") with self.camera_lock: self.capture = cv2.VideoCapture(uri) while not self.quit_thread: if self.connection_attempts > 50: wx.CallAfter(self.camera_error_webcam) with self.camera_lock: if self.capture is None: # No capture the thread dies. return try: ret = self.capture.grab() except AttributeError: continue if not ret: if self.attempt_recovery(): continue else: return ret, frame = self.capture.retrieve() if not ret or frame is None: if self.attempt_recovery(): continue else: return self.frame_attempts = 0 self.connection_attempts = 0 self.last_raw = self.current_raw self.current_raw = frame if not raw and \ self.fisheye_k is not None and \ self.fisheye_d is not None and \ self.device is not None and \ self.setting.correction_fisheye: # Unfisheye the drawing import numpy as np K = np.array(self.fisheye_k) D = np.array(self.fisheye_d) DIM = frame.shape[:2][::-1] map1, map2 = cv2.fisheye.initUndistortRectifyMap( K, D, np.eye(3), K, DIM, cv2.CV_16SC2) frame = cv2.remap(frame, map1, map2, interpolation=cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT) if not raw and self.device is not None and self.setting.correction_perspective: # Perspective the drawing. bed_width = self.device.bed_width * 2 bed_height = self.device.bed_height * 2 width, height = frame.shape[:2][::-1] import numpy as np if self.perspective is None: rect = np.array([[0, 0], [width - 1, 0], [width - 1, height - 1], [0, height - 1]], dtype="float32") else: rect = np.array(self.perspective, dtype="float32") dst = np.array( [[0, 0], [bed_width - 1, 0], [bed_width - 1, bed_height - 1], [0, bed_height - 1]], dtype="float32") M = cv2.getPerspectiveTransform(rect, dst) frame = cv2.warpPerspective(frame, M, (bed_width, bed_height)) frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) self.last_frame = self.current_frame self.current_frame = frame self.frame_index += 1 if self.capture is not None: self.capture.release() self.capture = None if self.device is not None: self.device.signal("camera_finalize") def reset_perspective(self, event): """ Reset the perspective settings. :param event: :return: """ self.perspective = None self.setting.perspective = '' def reset_fisheye(self, event): """ Reset the fisheye settings. :param event: :return: """ self.fisheye_k = None self.fisheye_d = None self.setting.fisheye = '' def on_erase(self, event): """ Erase camera view. :param event: :return: """ pass def on_paint(self, event): """ Paint camera view. :param event: :return: """ try: wx.BufferedPaintDC(self.display_camera, self._Buffer) except RuntimeError: pass def on_update_buffer(self, event=None): """ Draw Camera view. :param event: :return: """ if self.frame_bitmap is None: return # Need the bitmap to refresh. dm = self.device.draw_mode dc = wx.MemoryDC() dc.SelectObject(self._Buffer) dc.Clear() w, h = dc.Size if dm & DRAW_MODE_FLIPXY != 0: dc.SetUserScale(-1, -1) dc.SetLogicalOrigin(w, h) dc.SetBackground(wx.WHITE_BRUSH) gc = wx.GraphicsContext.Create(dc) # Can crash at bitmap okay gc.SetTransform( wx.GraphicsContext.CreateMatrix(gc, ZMatrix(self.matrix))) gc.PushState() gc.DrawBitmap(self.frame_bitmap, 0, 0, self.image_width, self.image_height) if not self.setting.correction_perspective: if self.perspective is None: self.perspective = [0, 0], \ [self.image_width, 0], \ [self.image_width, self.image_height], \ [0, self.image_height] gc.SetPen(wx.BLACK_DASHED_PEN) gc.StrokeLines(self.perspective) gc.StrokeLine(self.perspective[0][0], self.perspective[0][1], self.perspective[3][0], self.perspective[3][1]) gc.SetPen(wx.BLUE_PEN) for p in self.perspective: half = CORNER_SIZE / 2 gc.StrokeLine(p[0] - half, p[1], p[0] + half, p[1]) gc.StrokeLine(p[0], p[1] - half, p[0], p[1] + half) gc.DrawEllipse(p[0] - half, p[1] - half, CORNER_SIZE, CORNER_SIZE) gc.PopState() if dm & DRAW_MODE_INVERT != 0: dc.Blit(0, 0, w, h, dc, 0, 0, wx.SRC_INVERT) gc.Destroy() del dc def convert_scene_to_window(self, position): """ Scene Matrix convert scene to window. :param position: :return: """ point = self.matrix.point_in_matrix_space(position) return point[0], point[1] def convert_window_to_scene(self, position): """ Scene Matrix convert window to scene. :param position: :return: """ point = self.matrix.point_in_inverse_space(position) return point[0], point[1] def on_mouse_move(self, event): """ Handle mouse movement. :param event: :return: """ if not event.Dragging(): return else: self.SetCursor(wx.Cursor(wx.CURSOR_HAND)) if self.previous_window_position is None: return pos = event.GetPosition() window_position = pos.x, pos.y scene_position = self.convert_window_to_scene( [window_position[0], window_position[1]]) sdx = (scene_position[0] - self.previous_scene_position[0]) sdy = (scene_position[1] - self.previous_scene_position[1]) wdx = (window_position[0] - self.previous_window_position[0]) wdy = (window_position[1] - self.previous_window_position[1]) if self.corner_drag is None: self.scene_post_pan(wdx, wdy) else: self.perspective[self.corner_drag][0] += sdx self.perspective[self.corner_drag][1] += sdy self.setting.perspective = repr(self.perspective) self.previous_window_position = window_position self.previous_scene_position = scene_position def on_mousewheel(self, event): """ Handle mouse wheel. Used for zooming. :param event: :return: """ rotation = event.GetWheelRotation() mouse = event.GetPosition() if self.device.device_root.mouse_zoom_invert: rotation = -rotation if rotation > 1: self.scene_post_scale(1.1, 1.1, mouse[0], mouse[1]) elif rotation < -1: self.scene_post_scale(0.9, 0.9, mouse[0], mouse[1]) def on_mouse_left_down(self, event): """ Handle mouse left down event. Used for adjusting perspective items. :param event: :return: """ self.previous_window_position = event.GetPosition() self.previous_scene_position = self.convert_window_to_scene( self.previous_window_position) self.corner_drag = None if self.perspective is not None: for i, p in enumerate(self.perspective): half = CORNER_SIZE / 2 if Point.distance(self.previous_scene_position, p) < half: self.corner_drag = i break def on_mouse_left_up(self, event): """ Handle Mouse Left Up. Drag Ends. :param event: :return: """ self.SetCursor(wx.Cursor(wx.CURSOR_ARROW)) self.previous_window_position = None self.previous_scene_position = None self.corner_drag = None def on_mouse_middle_down(self, event): """ Handle mouse middle down Panning. :param event: :return: """ self.SetCursor(wx.Cursor(wx.CURSOR_HAND)) self.previous_window_position = event.GetPosition() self.previous_scene_position = self.convert_window_to_scene( self.previous_window_position) def on_mouse_middle_up(self, event): """ Handle mouse middle up. Pan ends. :param event: :return: """ self.SetCursor(wx.Cursor(wx.CURSOR_ARROW)) self.previous_window_position = None self.previous_scene_position = None def scene_post_pan(self, px, py): """ Scene Pan. :param px: :param py: :return: """ self.matrix.post_translate(px, py) self.on_update_buffer() def scene_post_scale(self, sx, sy=None, ax=0, ay=0): """ Scene Zoom. :param sx: :param sy: :param ax: :param ay: :return: """ self.matrix.post_scale(sx, sy, ax, ay) self.on_update_buffer() def update_in_gui_thread(self): """ Redraw on the GUI thread. :return: """ self.on_update_buffer() try: self.Refresh(True) self.Update() except RuntimeError: pass def on_check_perspective(self, event): """ Perspective checked. Turns on/off :param event: :return: """ self.setting.correction_perspective = self.check_perspective.GetValue() def on_check_fisheye(self, event): """ Fisheye checked. Turns on/off. :param event: :return: """ self.setting.correction_fisheye = self.check_fisheye.GetValue() def on_button_update(self, event): # wxGlade: CameraInterface.<event_handler> """ Button update. Sets image background to main scene. :param event: :return: """ frame = self.device.last_signal('camera_frame') if frame is not None: frame = frame[0] buffer = wx.Bitmap.FromBuffer(self.image_width, self.image_height, frame) self.device.signal('background', buffer) def on_button_export(self, event): # wxGlade: CameraInterface.<event_handler> """ Button export. Sends an image to the scene as an exported object. :param event: :return: """ frame = self.device.last_signal('camera_frame') if frame is not None: elements = self.device.device_root.elements frame = frame[0] from PIL import Image img = Image.fromarray(frame) obj = SVGImage() obj.image = img obj.image_width = self.image_width obj.image_height = self.image_height elements.add_elem(obj) def on_button_reconnect(self, event): # wxGlade: CameraInterface.<event_handler> self.quit_thread = True def on_slider_fps(self, event): # wxGlade: CameraInterface.<event_handler> """ Adjusts the camera FPS. If set to 0, this will be a frame each 5 seconds. :param event: :return: """ fps = self.slider_fps.GetValue() if fps == 0: tick = 5 else: tick = 1.0 / fps self.setting.fps = fps self.interval = tick def on_button_detect(self, event): # wxGlade: CameraInterface.<event_handler> """ Attempts to locate 6x9 checkerboard pattern for OpenCV to correct the fisheye pattern. :param event: :return: """ if self.fetch_job is not None: self.fetch_job.cancel() # TODO: This won't work anymore. self.device.signal('camera_frame_raw', None) self.fetch_job = self.device.add_job(self.on_camera_frame_raw, args=True, times=1, interval=0)