YOLOV8視窗專案

      在〈YOLOV8視窗專案〉中尚無留言

本專案以 YOLOV8 加上 QT6 視窗程式,即時偵測照片中的物件。請先下載 ui_mainwindow.ui,置於專案下的 ui 目錄。

安裝套件

請先安裝如下套件

pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 --no-cache-dir
pip install pyside6 ultralytics opencv-python

ModelThread

載入模型可能需要一段時間,所以使用新執行緒載入。

from PySide6.QtCore import QThread, Signal
from ultralytics import YOLO
class ModelThread(QThread):
    callback = Signal(object)
    def __init__(self, parent=None):
        super().__init__(parent)
    def run(self):
        model=YOLO("./yolov8n.pt")
        self.callback.emit(model)

PictureThread

此段是載入檔案中的圖片,如果檔案很多,載入時間也會很久,所以也交由新執行緒載入。

import os
from PIL import Image, ImageQt
from PySide6.QtCore import QThread, Signal

class PictureThread(QThread):
    callback=Signal(object)
    interrupted=Signal(object)
    def __init__(self, path, parent=None):
        super().__init__(parent)
        self.path=path
        self.runFlag=True
    def run(self):
        files=[]
        for file in os.listdir(self.path):
            tmp=file.lower()
            if tmp.endswith('.jpg') or tmp.endswith('.png'):
                files.append(os.path.join(self.path,file))
        index=0
        total=len(files)
        #底下是為了中途中斷執行緒,所以要加 self.runFlag
        while index < total and self.runFlag:
            # 使用 cv2.resize 效能較差
            # img=cv2.imdecode(
            #     np.fromfile(files[index], np.uint8),
            #     cv2.IMREAD_COLOR
            # )[:,:,::-1].copy()
            # img=cv2.resize(img, (250,187), interpolation=cv2.INTER_LINEAR)
            # pix = QPixmap(
            #     QImage(img,
            #            img.shape[1],
            #            img.shape[0],
            #            img.shape[1] * 3,
            #            QImage.Format.Format_RGB888
            #            )
            # )

            #使用 pix.scaled 效能也不好
            #pix=QPixmap(files[index])
            #pix=pix.scaled(250,187)

            #使用 Pillow 效能較高
            pil = Image.open(files[index])
            pil.thumbnail((250, 187))
            pix=ImageQt.toqpixmap(ImageQt.fromqimage(ImageQt.toqimage(pil)))

            pix.tag=files[index]
            self.callback.emit(pix)
            index+=1
            #QThread.msleep(10)#pyqt5要停一下,否則 UI 無法立即顯示, 但 pyside 6 不用停

        if index < total:
            self.interrupted.emit(True)
        else:
            self.interrupted.emit(False)

DetectThread

此段為辨識圖片代碼。

import platform
from PIL import Image, ImageFont, ImageDraw
from PySide6.QtCore import QThread, Signal
import numpy as np
import cv2
class DetectThread(QThread):
    callback = Signal(object)
    def __init__(self, model, file,parent=None):
        super().__init__(parent)
        self.model=model
        self.file=file
    def text(self, img, text, xy=(0, 0), color=(0, 0, 0), size=12):
        pil = Image.fromarray(img)
        s = platform.system()
        if s == "Linux":
            font = ImageFont.truetype(
                '/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc',
                size)
        elif s == "Darwin":
            font = ImageFont.truetype('....', size)
        else:
            font = ImageFont.truetype('simsun.ttc', size)
        ImageDraw.Draw(pil).text(xy, text, font=font, fill=color)
        return np.array(pil)

    def run(self):
        img=cv2.imdecode(
            np.fromfile(self.file, dtype=np.uint8),
            cv2.IMREAD_COLOR
        )[:,:,::-1].copy()
        results=self.model.predict(img)[0]
        names=[results.names[int(i.cpu().numpy())] for i in results.boxes.cls]
        boxes=results.boxes.xyxy
        for box, name in zip(boxes, names):
            box=box.cpu().numpy()
            x1 = int(box[0])
            y1 = int(box[1])
            x2 = int(box[2])
            y2 = int(box[3])
            img = cv2.rectangle(img, (x1, y1), (x2, y2), (0,255,0), 3)
            img = self.text(img, name, (x1, y1-20), color=(0,0,255), size=100)
        self.callback.emit(img)#傳回 numpy 格式

主程式

主程式代碼如下

import sys
import time
from PySide6.QtCore import QSize
from PySide6.QtGui import QIcon, QPixmap, QImage
from PySide6.QtWidgets import QMainWindow, QApplication, QPushButton, QListWidgetItem, QFileDialog
from DetectThread import DetectThread
from ModelThread import ModelThread
from PictureThread import PictureThread
from ui.ui_mainwindow import Ui_MainWindow

class YoloPicture(QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setupUi(self)
        #預設的圖片目錄
        self.path="D:/pictures/primitive/2020/20200126_Philippines_day5_Boracay"
        self.lblPath.setText(self.path)
        self.pictureThread=None
        #載入 Yolov8 模型
        self.lblStatus.setText("載入模型中....")
        self.modelThread=ModelThread()
        self.modelThread.callback.connect(self.modelThreadCallback)
        self.modelThread.start()
        self.btnPath.clicked.connect(self.btnPathClick)
    def modelThreadCallback(self, model):
        self.lblStatus.setText("載入模型完成")
        self.model=model

        #載入圖片
        self.startPictureThread()
    def btnPathClick(self):
        self.path=QFileDialog.getExistingDirectory()
        if self.path!='':
            if self.pictureThread is None:
                self.path = self.path.replace("\\", "/")
                self.lblPath.setText(self.path)
                self.listWidget.clear()
                self.startPictureThread()
            else:
                self.pictureThread.runFlag = False
    def startPictureThread(self):
        self.pictureThread = PictureThread(self.path)
        self.pictureThread.callback.connect(self.pictureThreadCallback)
        self.pictureThread.interrupted.connect(self.pictureThreadInterrupted)
        self.pictureThread.start()
    def pictureThreadCallback(self, pix):
        btn=QPushButton()
        btn.setIcon(QIcon(pix))
        btn.setIconSize(QSize(250,187))
        btn.tag=pix.tag
        btn.clicked.connect(self.btnClick)
        item=QListWidgetItem()
        item.setSizeHint(QSize(250,187))
        self.listWidget.addItem(item)
        self.listWidget.setItemWidget(item, btn)
    def pictureThreadInterrupted(self, interrupted):
        if interrupted:
            self.path = self.path.replace("\\", "/")
            self.lblPath.setText(self.path)
            self.listWidget.clear()
            self.startPictureThread()
        else:
            self.pictureThread=None

    def btnClick(self):
        btn=self.sender()
        self.lblStatus.setText('yolov8 辨識中...')
        self.t1=time.time()
        self.detectThread=DetectThread(self.model, btn.tag)
        self.detectThread.callback.connect(self.detectThreadCallback)
        self.detectThread.start()
    def detectThreadCallback(self, img):
        self.t2=time.time()
        self.lblStatus.setText(f'辨識時間 : {self.t2-self.t1:.2f}秒')
        img=img[:,:,::-1].copy()
        pix=QPixmap(
            QImage(
                img,
                img.shape[1],#寬
                img.shape[0],#高
                img.shape[1]*3,
                QImage.Format.Format_BGR888
            )
        )
        pr=pix.width()/pix.height()
        lr=self.lblImg.width()/self.lblImg.height()
        if pr>lr:
            pix=pix.scaled(self.lblImg.width(), int(self.lblImg.width()/pr))
        else:
            pix=pix.scaled(int(self.lblImg.height()*lr), self.lblImg.height())
        self.lblImg.setPixmap(pix)
    def closeEvent(self, event):
        if self.pictureThread is not None:
            self.pictureThread.runFlag=False
            time.sleep(0.01)
app=QApplication(sys.argv)
w=YoloPicture()
w.show()
app.exec()

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *