➣ Reading Time: 12 minutes

看完這篇文章你會得到的成果圖

這篇文章,主要是設計給我自己要用的 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 文章後再來閱讀此文。

設計我們的 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

⭐Python PyQt5 相關文章整理⭐:
⭐基礎知識與架構篇⭐:
1.【PyQt5】Day 1 – 安裝 PyQt,建立自己的第一支 PyQt5 程式
2.【PyQt5】Day 2 – 利用 Qt designer 建立第一支有自己介面的 PyQt5 程式
3.【PyQt5】Day 4 – 重要的 Qt 程式邏輯觀念,務必先有此觀念後面才會懂自己在幹嘛
4.【PyQt5】Day 5 – 開始來設計我們的 controller.py,改以「程式角度」來說明如何建立 PyQt 的系統
⭐基本元件應用篇⭐:
1.【PyQt5】Day 6 – 我們的第一個 output 手段 – Qlabel
2.【PyQt5】Day 7 – 我們的第一個 input 手段 – QPushButton
3.【PyQt5】Day 8 – 我們的第二個 input 手段 – QLineEdit
4.【PyQt5】Day 9 – 以 QLineEdit, QTextEdit, QPlainTextEdit 作為文字的輸入
6.【PyQt5】Day 14 - 使用 QSlider 製作可拖曳的滑條
8.【PyQt5】Day 19 - 使用 QProgressBar,製作進度條的功能
⭐介面系統控制篇⭐:
5.【PyQt5】Day 10 – 以 QFileDialog 讀取系統的檔案、資料夾
7.【PyQt5】Day 18 / Project 使用 QTimer,自製碼表(計時器) PyQt5 stopwatch DIY
1.【PyQt5】Day 20 - PyQt 最重要的 QThread 概念 / 為什麼 windows, mac, ubuntu (linux) 程式會「沒有回應」?
2.【PyQt5】Day 21 – 透過 PyQt 實現滑鼠監聽總整理,完全掌握滑鼠控制 (listen mouse)
3.【PyQt5】Day 22 – PyQt 視窗的個性化/屬性控制 setWindowFlags,禁止放大縮小、永遠顯示於最上層/最下層
4.【PyQt5】Day 23 – 使用系統內建的調色盤 QColorDialog,來替我們選擇顏色 QColor (Color Picker)
5.【PyQt5】Day 24 / Project 偵測滑鼠目前指示顏色的小工具 (滴管工具), 利用 QCursor 偵測滑鼠, QApplication 取得截圖
⭐影像處理篇⭐:
1.【PyQt5】Day 11 – 以 Qlabel 在 PyQt 中顯示圖片 (基於 QImage 使用 OpenCV)
2.【PyQt5】Day 12 – 建立一個可以縮放圖片大小的顯示器 (基於 QImage 使用 OpenCV)
3.【PyQt5】Day 13 – 使用 QVBoxLayout, QscrollArea 製作出捲軸,以高解析度檢視圖片 (基於 QImage 使用 OpenCV)
4.【PyQt5】Day 15 / Project 與檔案功能整合,製作出可讀取圖片並可縮放的 UI 介面 (使用 PyQt + OpenCV)
5.【PyQt5】Day 16 - 在 PyQt5 中取得圖片座標 (滑鼠位置) mousePressEvent,觀察圖片在 Qt 中產生的方式,對原圖進行座標換算處理
6.【PyQt5】Day 17 / Project 製作標註 roi 工具, 開始導入 OpenCV 作為繪圖引擎, 在圖上畫點並顯示座標
⭐project 篇⭐:
1.【PyQt5】Day 25 / Project 自己做一個影片播放器 DIY video player (結合 PyQt + OpenCV)
2.【PyQt5】Day 26 / Project 替我們影片播放器增加一個顯示進度的滑條 video player add slider (與昨日 bottleneck 處理細節)
3.【PyQt5】Day 27 / Project 製作影片 ROI 標註工具 (PyQt 結合 OpenCV 在圖上畫點畫線)
4.【PyQt5】Day 28 / final project – 1 來搞一個自己的 photoshop 吧!UI 篇 + 純程式架構篇 (結合 PyQt + OpenCV)
5.【PyQt5】Day 29 / final project – 2 來搞一個自己的 photoshop 吧!後段程式細節篇 (結合 PyQt + OpenCV)
6.【PyQt5】Day 30 / final project – 3 來搞一個自己的 photoshop 吧!把每個方法封裝起來製作出還原功能吧! (結合 PyQt + OpenCV)
⭐打包程式篇⭐:
1.【PyQt5】Day 3 – 用 pyinstaller 將 python 程式打包,把每天的成果分享給你的親朋好友
⭐【喜歡我的文章嗎? 歡迎幫我按讚~ 讓基金會請創作者喝一杯咖啡!
如果喜歡我的文章,請幫我在下方【按五下Like】 (Google, Facebook 免註冊),會由 「LikeCoin」 贊助作者鼓勵繼續創作,讀者們「只需幫忙按讚,完全不用出錢」哦!

likecoin-steps