【没钱买ps,PyQt自己写】Day 27 - project / 制作影片 ROI 标注工具 (PyQt 结合 OpenCV 在图上画点画线)

看完这篇文章你会得到的成果图

这篇文章,主要是设计给我自己要用的 Video Player 画 ROI 工具。
所以很多功能都是替我自己客制化。

此篇文章的范例程序码 github

https://github.com/howarder3/ironman2021_PyQt5_photoshop/tree/main/day27_video_roi_project

之前内容的重点复习 (前情提要)

我们接下来的讨论,会基於读者已经先读过我 day5 文章 的架构下去进行程序设计
如果还不清楚我程序设计的逻辑 (UI.py、controller.py、start.py 分别在干麻)
建议先阅读 day5 文章後再来阅读此文。

https://www.wongwonggoods.com/python/pyqt5-5/

设计我们的 UI

这篇文章由於是我在撰写 day25 的时候另外写的,还没有做出 day26 的优化更新,
如果有兴趣的可以自己再改,这边只专注在讲新增的功能

主要就是新增滑条的部分,新元素的名称:

  • self.button_clear_points:清除目前有的点
  • self.button_generate_rois:产生 roi 用按钮
  • self.text_save_points:显示储存的点
  • self.text_output_rois:显示 roi 结果

转换 day27.ui -> UI.py

pyuic5 -x day27.ui -o UI.py

执行看看 UI.py 画面是否如同我们想像

一样,这程序只有介面 (视觉上的呈现),没有任何互动功能

  • 看看我们制作出来的介面
python UI.py

设计我们的 controller

video_controller 新增功能

连结按键

连结这两个按键至两个对应的 function

def set_video_player(self):
    self.ui.button_clear_points.clicked.connect(self.clear_points)
    self.ui.button_generate_rois.clicked.connect(self.generate_rois)

以按键更新文字框

按下按键後,就要进行对应的文字更新,

  • 如果是清除键,除了文字清空外,也要把储存的点清空。
  • 如果是产生 roi 键,就把现有的点输出成我要的格式。
def clear_points(self):
    self.list_collect_points = []
    self.__update_text_show_points()

def __update_text_show_points(self):
    msg = "Current points (right click to return origin):\n"
    for ele in self.list_collect_points:
        msg += f"({ele[0]},{ele[1]})\n"
    self.ui.text_save_points.setText(msg)
	
def generate_rois(self):
    msg = "[\n"
    for ele in self.list_collect_points:
        msg += f"[{ele[0]},{ele[1]}],\n"
    msg += "]"
    self.ui.text_output_rois.setText(msg)

设定滑鼠控制,建立「画点画线」的机制

替我们的 Qlabel 定义一个按键 function,
侦测 Qlabel 上的滑鼠点击事件,并储存座标进 self.list_collect_points 里面。

新增点之後,更新点至显示画面。

def mouse_press_event(self, event):
    print(f"[show_mouse_press] {event.x()=}, {event.y()=}, {event.button()=}")
    norm_x = event.x()/self.qpixmap.width()
    norm_y = event.y()/self.qpixmap.height()
    if event.button() == 2: # right clicked
        self.list_collect_points.append(self.list_collect_points[0])
        self.__update_text_show_points()

def __update_points_onscreen(self, frame):
    if len(self.list_collect_points) == 0:
        pass
    else: # len(list) >= 1
        # first points
        frame = opencv_engine.draw_point(frame, point=self.list_collect_points[0], color = (0, 0, 255)) # red
        # if len = 1, no lines
        for idx in range(1, len(self.list_collect_points)):
            frame = opencv_engine.draw_point(frame, point=self.list_collect_points[idx], color = (0, 0, 255)) # red
            frame = opencv_engine.draw_line(frame, start_point =self.list_collect_points[idx-1], end_point=self.list_collect_points[idx], color = (0, 255, 0)) # green

    return frame

这边「画点画线」的功能,我们去 opencv_engine 新增

新增画点画线功能,使用 OpenCV

class opencv_engine(object):
    @staticmethod
    def norm_point_to_int(img, point):
        img_height, img_width, img_channel = img.shape
        return (int(img_width*point[0]), int(img_height*point[1]))

    @staticmethod
    def draw_point(img, point=(0, 0), color = (0, 0, 255)): # red
        point = opencv_engine.norm_point_to_int(img, point)
        # print(f"get {point=}")
        point_size = 10
        thickness = 4
        return cv2.circle(img, point, point_size, color, thickness)

    @staticmethod
    def draw_line(img, start_point = (0, 0), end_point = (0, 0), color = (0, 255, 0)): # green
        start_point = opencv_engine.norm_point_to_int(img, start_point)
        end_point = opencv_engine.norm_point_to_int(img, end_point)
        thickness = 3 # width
        return cv2.line(img, start_point, end_point, color, thickness)

测试结果

好啦,我自己要用的话 Video Player 画 ROI 工具就这样完成了!!!

Reference


★ 本文也同步发於我的个人网站(会有内容目录与显示各个小节,阅读起来更流畅):【PyQt5】Day 27 project / 制作影片 ROI 标注工具 (PyQt 结合 OpenCV 在图上画点画线)


<<:  28 | 【区块组合套件介绍】Kadence Blocks

>>:  应用系统的防护基准 - 委外注意事项

Python - 根据输入的英文字母排列出有意义的单词-参考笔记

Python - 根据输入的英文字母排列出有意义的单词-参考笔记 参考资料 Day26- pytho...

Day 8 | 比较漂亮的清单-客制化Adapter

Adapter客制化 当需要图文并茂时,就需要客制化Adapter。 建立资料 data class...

【Day29】iOS相关分享

其实在github上面找 awesome 啥的可能就很容易找到相关的资料整理 另外,知乎上面的问题&...

TypeScript 能手养成之旅 Day 14 特殊型别(2) - Any & Unknown

前言 今天要来了解 any 和 unknow ,这两个会放在一起说明是因为性质和用法很相似,但却有些...

Day-07 说明Ruby 的include, extend,require差别?

Ruby 里面有多种引入 Module 方式,他们的差别是什麽呢? Include: 当一个 cla...