From 6f25ebbc9ef75aee61145084582d334b680d167b Mon Sep 17 00:00:00 2001 From: Colombo Date: Sun, 31 May 2020 18:00:39 +0400 Subject: [PATCH] XSegEditor: added button "view XSeg mask overlay face" --- XSegEditor/QIconDB.py | 4 +- XSegEditor/QStringDB.py | 5 + XSegEditor/XSegEditor.py | 111 +++++++++++++-------- XSegEditor/gfx/icons/view_xseg_overlay.png | Bin 0 -> 11785 bytes 4 files changed, 80 insertions(+), 40 deletions(-) create mode 100644 XSegEditor/gfx/icons/view_xseg_overlay.png diff --git a/XSegEditor/QIconDB.py b/XSegEditor/QIconDB.py index f4be98b..a2427c3 100644 --- a/XSegEditor/QIconDB.py +++ b/XSegEditor/QIconDB.py @@ -20,4 +20,6 @@ class QIconDB(): QIconDB.pt_edit_mode = QIcon ( str(icon_path / 'pt_edit_mode.png') ) QIconDB.view_lock_center = QIcon ( str(icon_path / 'view_lock_center.png') ) QIconDB.view_baked = QIcon ( str(icon_path / 'view_baked.png') ) - QIconDB.view_xseg = QIcon ( str(icon_path / 'view_xseg.png') ) \ No newline at end of file + QIconDB.view_xseg = QIcon ( str(icon_path / 'view_xseg.png') ) + QIconDB.view_xseg_overlay = QIcon ( str(icon_path / 'view_xseg_overlay.png') ) + \ No newline at end of file diff --git a/XSegEditor/QStringDB.py b/XSegEditor/QStringDB.py index c825a55..55d537a 100644 --- a/XSegEditor/QStringDB.py +++ b/XSegEditor/QStringDB.py @@ -34,6 +34,11 @@ class QStringDB(): 'ru' : 'Посмотреть тренированную XSeg маску', 'zh' : '查看导入后的XSeg遮罩', }[lang] + + QStringDB.btn_view_xseg_overlay_mask_tip = { 'en' : 'View trained XSeg mask overlay face', + 'ru' : 'Посмотреть тренированную XSeg маску поверх лица', + 'zh' : '查看导入后的XSeg遮罩于脸上方', + }[lang] QStringDB.btn_poly_type_include_tip = { 'en' : 'Poly include mode', 'ru' : 'Режим полигонов - включение', diff --git a/XSegEditor/XSegEditor.py b/XSegEditor/XSegEditor.py index 0e06b10..3e396f4 100644 --- a/XSegEditor/XSegEditor.py +++ b/XSegEditor/XSegEditor.py @@ -35,6 +35,7 @@ class OpMode(IntEnum): EDIT_PTS = 2 VIEW_BAKED = 3 VIEW_XSEG_MASK = 4 + VIEW_XSEG_OVERLAY_MASK = 5 class PTEditMode(IntEnum): MOVE = 0 @@ -89,39 +90,39 @@ class ImagePreviewSequenceBar(QFrame): def get_preview_images_count(self): return self.preview_images_count - def update_images(self, prev_q_imgs=None, next_q_imgs=None): + def update_images(self, prev_imgs=None, next_imgs=None): # Fix arrays - if prev_q_imgs is None: - prev_q_imgs = [] + if prev_imgs is None: + prev_imgs = [] prev_img_conts_len = len(self.prev_img_conts) - prev_q_imgs_len = len(prev_q_imgs) + prev_q_imgs_len = len(prev_imgs) if prev_q_imgs_len < prev_img_conts_len: for i in range ( prev_img_conts_len - prev_q_imgs_len ): - prev_q_imgs.append(None) + prev_imgs.append(None) elif prev_q_imgs_len > prev_img_conts_len: - prev_q_imgs = prev_q_imgs[:prev_img_conts_len] + prev_imgs = prev_imgs[:prev_img_conts_len] - if next_q_imgs is None: - next_q_imgs = [] + if next_imgs is None: + next_imgs = [] next_img_conts_len = len(self.next_img_conts) - next_q_imgs_len = len(next_q_imgs) + next_q_imgs_len = len(next_imgs) if next_q_imgs_len < next_img_conts_len: for i in range ( next_img_conts_len - next_q_imgs_len ): - next_q_imgs.append(None) + next_imgs.append(None) elif next_q_imgs_len > next_img_conts_len: - next_q_imgs = next_q_imgs[:next_img_conts_len] + next_imgs = next_imgs[:next_img_conts_len] - for i,q_img in enumerate(prev_q_imgs): - if q_img is None: + for i,img in enumerate(prev_imgs): + if img is None: self.prev_img_conts[i].setPixmap( self.black_q_pixmap ) else: - self.prev_img_conts[i].setPixmap( QPixmap.fromImage(q_img) ) + self.prev_img_conts[i].setPixmap( QPixmap.fromImage( QImage_from_np(img) ) ) - for i,q_img in enumerate(next_q_imgs): - if q_img is None: + for i,img in enumerate(next_imgs): + if img is None: self.next_img_conts[i].setPixmap( self.black_q_pixmap ) else: - self.next_img_conts[i].setPixmap( QPixmap.fromImage(q_img) ) + self.next_img_conts[i].setPixmap( QPixmap.fromImage( QImage_from_np(img) ) ) class ColorScheme(): def __init__(self, unselected_color, selected_color, outline_color, outline_width, pt_outline_color, cross_cursor): @@ -255,6 +256,11 @@ class QCanvasControlsRightBar(QFrame): self.btn_view_xseg_mask_act = QActionEx( QIconDB.view_xseg, QStringDB.btn_view_xseg_mask_tip, shortcut='5', shortcut_in_tooltip=True, is_checkable=True) btn_view_xseg_mask.setDefaultAction(self.btn_view_xseg_mask_act) btn_view_xseg_mask.setIconSize(QUIConfig.icon_q_size) + + btn_view_xseg_overlay_mask = QToolButton() + self.btn_view_xseg_overlay_mask_act = QActionEx( QIconDB.view_xseg_overlay, QStringDB.btn_view_xseg_overlay_mask_tip, shortcut='6', shortcut_in_tooltip=True, is_checkable=True) + btn_view_xseg_overlay_mask.setDefaultAction(self.btn_view_xseg_overlay_mask_act) + btn_view_xseg_overlay_mask.setIconSize(QUIConfig.icon_q_size) self.btn_poly_color_act_grp = QActionGroup (self) self.btn_poly_color_act_grp.addAction(self.btn_poly_color_red_act) @@ -262,6 +268,7 @@ class QCanvasControlsRightBar(QFrame): self.btn_poly_color_act_grp.addAction(self.btn_poly_color_blue_act) self.btn_poly_color_act_grp.addAction(self.btn_view_baked_mask_act) self.btn_poly_color_act_grp.addAction(self.btn_view_xseg_mask_act) + self.btn_poly_color_act_grp.addAction(self.btn_view_xseg_overlay_mask_act) self.btn_poly_color_act_grp.setExclusive(True) #============================================== btn_view_lock_center = QToolButton() @@ -275,6 +282,7 @@ class QCanvasControlsRightBar(QFrame): controls_bar_frame1_l.addWidget ( btn_poly_color_blue ) controls_bar_frame1_l.addWidget ( btn_view_baked_mask ) controls_bar_frame1_l.addWidget ( btn_view_xseg_mask ) + controls_bar_frame1_l.addWidget ( btn_view_xseg_overlay_mask ) controls_bar_frame1 = QFrame() controls_bar_frame1.setFrameShape(QFrame.StyledPanel) controls_bar_frame1.setSizePolicy (QSizePolicy.Fixed, QSizePolicy.Fixed) @@ -307,7 +315,8 @@ class QCanvasOperator(QWidget): self.cbar.btn_poly_color_blue_act.triggered.connect ( lambda : self.set_color_scheme_id(2) ) self.cbar.btn_view_baked_mask_act.toggled.connect ( lambda : self.set_op_mode(OpMode.VIEW_BAKED) ) self.cbar.btn_view_xseg_mask_act.toggled.connect ( self.set_view_xseg_mask ) - + self.cbar.btn_view_xseg_overlay_mask_act.toggled.connect ( self.set_view_xseg_overlay_mask ) + self.cbar.btn_poly_type_include_act.triggered.connect ( lambda : self.set_poly_include_type(SegIEPolyType.INCLUDE) ) self.cbar.btn_poly_type_exclude_act.triggered.connect ( lambda : self.set_poly_include_type(SegIEPolyType.EXCLUDE) ) @@ -328,17 +337,22 @@ class QCanvasOperator(QWidget): self.initialized = False self.last_state = None - def initialize(self, q_img, img_look_pt=None, view_scale=None, ie_polys=None, xseg_mask=None, canvas_config=None ): - self.q_img = q_img + def initialize(self, img, img_look_pt=None, view_scale=None, ie_polys=None, xseg_mask=None, canvas_config=None ): + q_img = self.q_img = QImage_from_np(img) self.img_pixmap = QPixmap.fromImage(q_img) self.xseg_mask_pixmap = None if xseg_mask is not None: - w,h = QSize_to_np ( q_img.size() ) + h,w,c = img.shape xseg_mask = cv2.resize(xseg_mask, (w,h), cv2.INTER_CUBIC) - xseg_mask = (imagelib.normalize_channels(xseg_mask, 1) * 255).astype(np.uint8) - self.xseg_mask_pixmap = QPixmap.fromImage(QImage_from_np(xseg_mask)) - + xseg_mask = imagelib.normalize_channels(xseg_mask, 1) + xseg_img = img.astype(np.float32)/255.0 + xseg_overlay_mask = xseg_img*(1-xseg_mask)*0.5 + xseg_img*xseg_mask + xseg_overlay_mask = np.clip(xseg_overlay_mask*255, 0, 255).astype(np.uint8) + xseg_mask = np.clip(xseg_mask*255, 0, 255).astype(np.uint8) + self.xseg_mask_pixmap = QPixmap.fromImage(QImage_from_np(xseg_mask)) + self.xseg_overlay_mask_pixmap = QPixmap.fromImage(QImage_from_np(xseg_overlay_mask)) + self.img_size = QSize_to_np (self.img_pixmap.size()) self.img_look_pt = img_look_pt @@ -389,7 +403,7 @@ class QCanvasOperator(QWidget): if self.op_mode == OpMode.DRAW_PTS: self.set_op_mode(OpMode.EDIT_PTS) - self.last_state = sn(op_mode = self.op_mode if self.op_mode in [OpMode.VIEW_BAKED, OpMode.VIEW_XSEG_MASK] else None, + self.last_state = sn(op_mode = self.op_mode if self.op_mode in [OpMode.VIEW_BAKED, OpMode.VIEW_XSEG_MASK, OpMode.VIEW_XSEG_OVERLAY_MASK] else None, color_scheme_id = self.color_scheme_id, ) @@ -521,7 +535,9 @@ class QCanvasOperator(QWidget): self.cbar.btn_view_baked_mask_act.setChecked(False) elif self.op_mode == OpMode.VIEW_XSEG_MASK: self.cbar.btn_view_xseg_mask_act.setChecked(False) - + elif self.op_mode == OpMode.VIEW_XSEG_OVERLAY_MASK: + self.cbar.btn_view_xseg_overlay_mask_act.setChecked(False) + self.op_mode = op_mode # Initialize new mode @@ -544,6 +560,9 @@ class QCanvasOperator(QWidget): self.img_baked_pixmap = QPixmap.fromImage(QImage_from_np(n)) elif op_mode == OpMode.VIEW_XSEG_MASK: self.cbar.btn_view_xseg_mask_act.setChecked(True) + elif op_mode == OpMode.VIEW_XSEG_OVERLAY_MASK: + self.cbar.btn_view_xseg_overlay_mask_act.setChecked(True) + if op_mode in [OpMode.DRAW_PTS, OpMode.EDIT_PTS]: self.mouse_op_poly_pt_id = None self.mouse_op_poly_edge_id = None @@ -617,6 +636,13 @@ class QCanvasOperator(QWidget): self.cbar.btn_view_xseg_mask_act.setChecked(is_checked ) + def set_view_xseg_overlay_mask(self, is_checked): + if is_checked: + self.set_op_mode(OpMode.VIEW_XSEG_OVERLAY_MASK) + else: + self.set_op_mode(OpMode.NONE) + + self.cbar.btn_view_xseg_overlay_mask_act.setChecked(is_checked ) # ==================================================================================== # ==================================================================================== @@ -936,6 +962,11 @@ class QCanvasOperator(QWidget): src_rect = QRect(0, 0, *self.img_size) dst_rect = self.img_to_cli_rect( src_rect ) qp.drawPixmap(dst_rect, self.xseg_mask_pixmap, src_rect) + elif self.op_mode == OpMode.VIEW_XSEG_OVERLAY_MASK: + if self.xseg_overlay_mask_pixmap is not None: + src_rect = QRect(0, 0, *self.img_size) + dst_rect = self.img_to_cli_rect( src_rect ) + qp.drawPixmap(dst_rect, self.xseg_overlay_mask_pixmap, src_rect) else: if self.img_pixmap is not None: src_rect = QRect(0, 0, *self.img_size) @@ -1061,6 +1092,7 @@ class QCanvas(QFrame): btn_poly_color_blue_act = self.canvas_control_right_bar.btn_poly_color_blue_act, btn_view_baked_mask_act = self.canvas_control_right_bar.btn_view_baked_mask_act, btn_view_xseg_mask_act = self.canvas_control_right_bar.btn_view_xseg_mask_act, + btn_view_xseg_overlay_mask_act = self.canvas_control_right_bar.btn_view_xseg_overlay_mask_act, btn_poly_color_act_grp = self.canvas_control_right_bar.btn_poly_color_act_grp, btn_view_lock_center_act = self.canvas_control_right_bar.btn_view_lock_center_act, @@ -1151,7 +1183,7 @@ class MainWindow(QXMainWindow): self.cfg_path = cfg_root_path / 'MainWindow_cfg.dat' self.cfg_dict = pickle.loads(self.cfg_path.read_bytes()) if self.cfg_path.exists() else {} - self.cached_QImages = {} + self.cached_images = {} self.cached_has_ie_polys = {} self.initialize_ui() @@ -1188,7 +1220,7 @@ class MainWindow(QXMainWindow): def update_cached_images (self, count=5): - d = self.cached_QImages + d = self.cached_images for image_path in self.image_paths_done[:-count]+self.image_paths[count:]: if image_path in d: @@ -1198,13 +1230,14 @@ class MainWindow(QXMainWindow): if image_path not in d: img = cv2_imread(image_path) if img is not None: - d[image_path] = QImage_from_np(img) + d[image_path] = img - def load_QImage(self, image_path): + def load_image(self, image_path): try: - img = self.cached_QImages.get(image_path, None) + img = self.cached_images.get(image_path, None) if img is None: - img = QImage_from_np(cv2_imread(image_path)) + img = cv2_imread(image_path) + self.cached_images[image_path] = img if img is None: io.log_err(f'Unable to load {image_path}') except: @@ -1214,10 +1247,10 @@ class MainWindow(QXMainWindow): def update_preview_bar(self): count = self.image_bar.get_preview_images_count() - d = self.cached_QImages - prev_q_imgs = [ d.get(image_path, None) for image_path in self.image_paths_done[-1:-count:-1] ] - next_q_imgs = [ d.get(image_path, None) for image_path in self.image_paths[:count] ] - self.image_bar.update_images(prev_q_imgs, next_q_imgs) + d = self.cached_images + prev_imgs = [ d.get(image_path, None) for image_path in self.image_paths_done[-1:-count:-1] ] + next_imgs = [ d.get(image_path, None) for image_path in self.image_paths[:count] ] + self.image_bar.update_images(prev_imgs, next_imgs) def canvas_initialize(self, image_path, only_has_polys=False): @@ -1230,11 +1263,11 @@ class MainWindow(QXMainWindow): ie_polys = dflimg.get_seg_ie_polys() xseg_mask = dflimg.get_xseg_mask() - q_img = self.load_QImage(image_path) - if q_img is None: + img = self.load_image(image_path) + if img is None: return False - self.canvas.op.initialize ( q_img, ie_polys=ie_polys, xseg_mask=xseg_mask ) + self.canvas.op.initialize ( img, ie_polys=ie_polys, xseg_mask=xseg_mask ) self.filename_label.setText(str(image_path.name)) diff --git a/XSegEditor/gfx/icons/view_xseg_overlay.png b/XSegEditor/gfx/icons/view_xseg_overlay.png new file mode 100644 index 0000000000000000000000000000000000000000..d188285f82c6bd736e7771d6f9f81fe3319f00db GIT binary patch literal 11785 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