JeVoisBase  1.22
JeVois Smart Embedded Machine Vision Toolkit Base Modules
Share this page:
Loading...
Searching...
No Matches
PyPostURetinex.py
Go to the documentation of this file.
1import pyjevois
2if pyjevois.pro: import libjevoispro as jevois
3else: import libjevois as jevois
4
5import numpy as np
6import cv2
7
8## Python DNN post-processor for filtered color image
9#
10# Renders a filtered color image (with retinex-adjusted colors) on top of the original image, in between two
11# user-draggable bars. Logic for the user bars here is converted from our ColorFiltering C++ module.
12#
13# URetinex-Net aims at recovering colors from very low light images. Hence, point your camera to a very dark area (e.g.,
14# under your desk) to see the image enhancement provided by URetinex-Net.
15#
16# One of the goals of this post-processor is to demosntrate correct handling of coordinate transforms between display
17# image, processing image, input tensor.
18#
19# @author Laurent Itti
20#
21# @email itti\@usc.edu
22# @address University of Southern California, HNB-07A, 3641 Watt Way, Los Angeles, CA 90089-2520, USA
23# @copyright Copyright (C) 2023 by Laurent Itti, iLab and the University of Southern California
24# @mainurl http://jevois.org
25# @supporturl http://jevois.org/doc
26# @otherurl http://iLab.usc.edu
27# @license GPL v3
28# @distribution Unrestricted
29# @restrictions None
30# @ingroup pydnn
32 # ###################################################################################################
33 ## Constructor
34 def __init__(self):
35 self.rgba_map = None
36 self.dragleft = False
37 self.dragright = False
38 self.left = -1.0e20
39 self.right = -1.0e20
40 self.pp = None
41
42 # ###################################################################################################
43 ## Get network outputs
44 def process(self, outs, preproc):
45 if len(outs) != 1: jevois.LERROR(f"Received {len(outs)} network outputs -- USING FIRST ONE")
46
47 # Save RGBA depth map for later display:
48 self.rgba_map = cv2.cvtColor(np.squeeze(outs[0] * 255).clip(0, 255).astype('uint8').transpose(1, 2, 0),
49 cv2.COLOR_RGB2RGBA)
50
51 # Compute overlay corner coords within the input image, for use in report():
52 self.tlx, self.tly, self.cw, self.ch = preproc.getUnscaledCropRect(0)
53
54 # We will need to do some coordinate conversion in report(), so keep a handle to the preproc:
55 self.pp = preproc
56
57 # ###################################################################################################
58 ## Report the latest results obtained by process() by drawing them
59 def report(self, outimg, helper, overlay, idle):
60
61 col = 0xffffff7f # ARGB color of the vertical lines and square handles
62 siz = 20 # size of the square handles, in image pixels
63
64 # The main thing here is to properly handle coordinates: For example
65 #
66 # - Display typically is 1920x1080 @ 0,0, or it could also be 4K
67 # - Captured video (full resolution stream for display) is typically 1920x1080 but could also be,
68 # e.g., 640x480 with scaling and translation to show up centered and as big as possible on the display
69 # - Captured video for DNN processing typically is 1024x512 @ 0,0 -> use helper.i2d() to translate/scale
70 # from image to display, or helper.d2i() from display to image
71 # - Input tensor (blob) for retinex processing typically is a rescaled and possibly letterboxed version
72 # of the processing image, e.g., to 320x180. Use preproc.b2i() or preproc.i2b()
73 # - Mouse coordinates are in screen coordinates. Here, our left and right drag handles will be in processing
74 # image coordinates, since helper.drawLine(), etc will internally call i2d() as needed.
75 #
76 # Yes, the logic below is not trivial, you need to follow it carefully. It works in a broad range of cases:
77 #
78 # - start the DNN module with dual-stream capture of 1920x1080 (for display) + 1024x512 (for processing),
79 # and the overlay and drawn handles should display correctly.
80 # - flip the 'letterbox' parameter of the pre-processor and check that graphics are still ok.
81 # - Then try the DNN module in 640x480, which uses a single capture stream, and its display is centered
82 # and scaled on the screen. Again flip the 'letterbox' parameter of the pre-processor and graphics should
83 # still look good.
84
85 if helper is not None and overlay and self.rgba_map is not None and self.pp is not None:
86 # Processing image dims:
87 iw, ih = self.pp.imagesize()
88
89 # Initialize the handles at 1/4 and 3/4 of image width on first video frame after module is loaded:
90 if self.left < -0.9e20:
91 self.left = 0.25 * iw
92 self.right = 0.75 * iw
93
94 # Make sure the handles do not overlap and/or get out of the image bounds:
95 if self.left > self.right - siz:
96 if self.dragright: self.left = self.right - siz
97 else: self.right = self.left + siz
98
99 self.left = max(siz, min(iw - siz * 2, self.left))
100 self.right = max(self.left + siz, min(iw - siz, self.right))
101
102 # Mask and draw the overlay. To achieve this, we convert the whole result image to RGBA and then assign a
103 # zero alpha channel to all pixels to the left of the 'left' bound and to the right of the 'right' bound.
104 # First we need to convert from image coords to blob coords:
105 blob_left, blob_top = self.pp.i2b(self.left, 0.0, 0)
106 blob_right, blob_bot = self.pp.i2b(self.right, ih, 0)
107
108 ovl = self.rgba_map
109 ovl[:, :int(blob_left), 3] = 0 # make left side transparent
110 ovl[:, int(blob_right):, 3] = 0 # make right side transparent
111
112 # Convert box coords from input image to display ("c" is the displayed camera image):
113 tl = helper.i2d(self.tlx, self.tly, "c")
114 wh = helper.i2ds(self.cw, self.ch, "c")
115
116 # Draw as a semi-transparent overlay. OpenGL will do scaling/stretching/blending as needed:
117 helper.drawImage("r", ovl, True, int(tl.x), int(tl.y), int(wh.x), int(wh.y), False, True)
118
119 # Draw drag handles:
120 ovtop = self.tly # top of the overlay image (including possible preproc letterboxing)
121 ovbot = self.tly + self.ch # bottom of overlay
122 ovmid = 0.5 * (ovtop + ovbot) # vertical midpoint for our handles
123
124 helper.drawLine(self.left, ovtop, self.left, ovbot, col)
125 helper.drawRect(self.left - siz, ovmid - siz/2, self.left, ovmid + siz/2, col, True)
126 helper.drawLine(self.right, ovtop, self.right, ovbot, col)
127 helper.drawRect(self.right, ovmid - siz/2, self.right + siz, ovmid + siz/2, col, True)
128
129 # Adjust the left and right handles if they get clicked and dragged:
130 mp = helper.getMousePos() # in screen coordinates
131 ip = helper.d2i(mp.x, mp.y, "c") # in image coordinates
132
133 if helper.isMouseClicked(0):
134 # Are we clicking on the left or right handle?
135 if ip.x > self.left-siz and ip.x < self.left and ip.y > (ih - siz)/2 and ip.y < (ih + siz)/2:
136 self.dragleft = True
137
138 if ip.x > self.right and ip.x < self.right+siz and ip.y > (ih - siz)/2 and ip.y < (ih + siz)/2:
139 self.dragright = True
140
141 if helper.isMouseDragging(0):
142 if self.dragleft: self.left = ip.x + 0.5 * siz
143 if self.dragright: self.right = ip.x - 0.5 * siz
144 # We will enforce validity of left and right on next frame, before we draw
145
146 if helper.isMouseReleased(0):
147 self.dragleft = False
148 self.dragright = False
Python DNN post-processor for filtered color image.
report(self, outimg, helper, overlay, idle)
Report the latest results obtained by process() by drawing them.
process(self, outs, preproc)
Get network outputs.