Tayaki
@Tayaki
Пытаюсь в Python

Как завершить поток, изменить значения для его работы и запустить снова?

Добрый вечер.

Создаю виджет, который проигрывает то или иное видео в зависимости от нажатой кнопки (open или open_from_camera) в родительском виджете: либо из файла, либо транслировать поток с веб-камеры.
При первой загрузке видео, никаких проблем нет: всё показывает как надо. При повторной загрузке из другого источника (из другого файла или переключение на вебкамеру), проигрывание первоначального видео продолжается, хотя путь к новому файлу меняется.
Подозреваю, что неправильно прерываю/останавливаю поток.
Пробовала terminate(), но, если верить документации(и моему посредственному английскому), после этого память не чистится, и вообще всё плохо и по-хорошему лучше не использовать эту функцию, ибо с потоком может произойти всё что угодно и в любой точке кода.
Поэтому вопрос: как правильно завершать поток, менять переменные для его работы и тут же запускать заново?
(ps Если честно, то уже даже не помню, почему выбрала именно поток (вроде вычитала под каким-то вопросом на stackoverflow или подсмотрела в каком-то примере). Если можно обойтись без него, а это вроде бы вполне возможно, потому что раньше работало без него и, причём, картинка загружалась куда быстрее, то как это сделать правильно?)

spoiler
Собственно, код (нужное выделю и прокомментирую):
from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *
import cv2 # OpenCV
import qimage2ndarray # for a memory leak
from dialog import *
     
class VideoWidget(QWidget):
    
    def __init__(self, root, **kwargs):
        super().__init__(root, **kwargs)
        self.main=root
        self.dialogOpenFromCamera = Dialog(self)
        self.initUI()

    @Slot(QImage)
    def setImage (self, image):
        self.label.setPixmap(QPixmap.fromImage(image))
    
    def initUI(self):
        self.setFixedSize(640,480)
        self.label = QLabel(self)
        self.label.setText("Загрузите видео")
        self.label.setAlignment(Qt.AlignCenter)
        self.layout = QVBoxLayout(self)
        self.layout.setContentsMargins(0,0,0,0)
        self.layout.addWidget(self.label)
        self.setLayout(self.layout)
        self.th = Thread(self)
        self.th.changePixmap.connect(self.setImage)

    def open(self):
        fileDialog = QFileDialog(self)
        supportedMimeTypes = ["video", "*.*"]
        fileDialog.setMimeTypeFilters(supportedMimeTypes)
        moviesLocation = QStandardPaths.writableLocation(QStandardPaths.MoviesLocation)
        fileDialog.setDirectory(moviesLocation)
        if fileDialog.exec_() == QDialog.Accepted: 
            if (self.th.way != fileDialog.selectedUrls()[0].toDisplayString()): # если выбран не тот же файл
                print ("\nВыбран новый файл: ", fileDialog.selectedUrls()) # проверка — выведем название выбранного файла
                if (self.th.way != None): #и если это не первый запуск (кажется, это избыточное условие, но пока проверяю всё) 
                    self.th.exit(0) #!!!прерывание потока
                    print ("Старый th.way: ", self.th.way) # проверка старого пути
                self.th.way = fileDialog.selectedUrls()[0].toDisplayString() #меняем путь в классе потока, чтобы следующий поток запустился с новым источником видео
                print ("Теперь th.way =", self.th.way) #проверка нынешнего пути в потоке
                self.th.start() # запуск потока снова

    def open_from_camera(self):
        self.dialogOpenFromCamera.exec_() # запуск формочки для ввода порта камеры (позже будет получение адреса ip-камеры). Сейчас self.dialogOpenFromCamera.way = 0
        if (self.th.way != self.dialogOpenFromCamera.way): # если путь потока и путь формы не совпадают
            if (self.th.way != None): #и если это не первый запуск потока
                self.th.exit(0) #!!!завершить поток
            self.th.way = self.dialogOpenFromCamera.way #изменить его путь
            self.th.start() #и запустить
            

class Thread (QThread):
    changePixmap = Signal(QImage)

    def __init__(self, root, **kwargs):
        super().__init__(root, **kwargs)
        self.main=root
        self.way=None
        
    def run(self):
        cap = cv2.VideoCapture(self.way)#"video_file.mp4")#0)
        while True:
            face_cascade= cv2.CascadeClassifier(r'face_cascade.xml')
            ret, frame = cap.read()
            if (ret == True):
                cap.set(3,640)
                cap.set(4, 480)
                frame = cv2.cvtColor (frame, cv2.COLOR_BGR2RGB)
                gray = cv2.cvtColor (frame, cv2.COLOR_BGR2GRAY)
                faces = face_cascade.detectMultiScale(gray, 1.3, 5)
                for (x,y,w,h) in faces:
                    frame = cv2.rectangle(frame,(x,y),(x+w,y+h),(255,0,0),2)
                    roi_gray = gray[y:y+h, x:x+w]
                    roi_color = frame[y:y+h, x:x+w]
                image = qimage2ndarray.array2qimage(frame)
                self.changePixmap.emit(image)


Вероятность в 99,9%, что вместоself.th.exit(0) должно быть что-то более адекватное и рабочее, но пока не пойму что.
Если нужно, прикреплю остальной код (main.py, dialog.py и face_cascade.xml).


Заранее спасибо за ответ! :)
  • Вопрос задан
  • 346 просмотров
Решения вопроса 1
sanya84
@sanya84
Фанатик Python 3
Вот так как вариант
import sys
from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *
import cv2 # OpenCV
import qimage2ndarray # for a memory leak


class ThreadCamera(QThread):
    status = True
    changePixmap = Signal(QImage)

    def __init__(self):
        super().__init__()
        self.capture = cv2.VideoCapture(0)

    def setStatus(self, status):
        self.status = status
    def captureRelease(self):
        self.capture.release()

    def run(self):

        while self.status:
            face_cascade = cv2.CascadeClassifier(r'haarcascade_frontalface_default.xml')
            ret, frame = self.capture.read()
            if (ret == True):
                self.capture.set(3,640)
                self.capture.set(4, 480)
                frame = cv2.cvtColor (frame, cv2.COLOR_BGR2RGB)
                gray = cv2.cvtColor (frame, cv2.COLOR_BGR2GRAY)
                faces = face_cascade.detectMultiScale(gray, 1.3, 5)
                for (x,y,w,h) in faces:
                    frame = cv2.rectangle(frame,(x,y),(x+w,y+h),(255,0,0),2)
                    roi_gray = gray[y:y+h, x:x+w]
                    roi_color = frame[y:y+h, x:x+w]
                image = qimage2ndarray.array2qimage(frame)
                self.changePixmap.emit(image)


class ThreadVideoFile(QThread):
    status = True
    changePixmap = Signal(QImage)

    def __init__(self, path):
        super().__init__()
        self.capture = cv2.VideoCapture(path)
    def set_status(self, status):
        self.status = status
    def captureRelease(self):
        self.capture.release()

    def run(self):

        while self.status:
            face_cascade = cv2.CascadeClassifier(r'haarcascade_frontalface_default.xml')
            ret, frame = self.capture.read()
            if (ret == True):
                self.capture.set(3,640)
                self.capture.set(4, 480)
                frame = cv2.cvtColor (frame, cv2.COLOR_BGR2RGB)
                gray = cv2.cvtColor (frame, cv2.COLOR_BGR2GRAY)
                faces = face_cascade.detectMultiScale(gray, 1.3, 5)
                for (x,y,w,h) in faces:
                    frame = cv2.rectangle(frame,(x,y),(x+w,y+h),(255,0,0),2)
                    roi_gray = gray[y:y+h, x:x+w]
                    roi_color = frame[y:y+h, x:x+w]
                image = qimage2ndarray.array2qimage(frame)
                self.changePixmap.emit(image)



class Dialog(QDialog):
    def __init__(self, parent):
        super().__init__()
        self.resize(400, 100)
        self.label = QLabel()
        self.label.setText("<center><h1>Выберите действие</h1></center>")

        self.button_open_camera = QPushButton('Камера')
        self.button_open_camera.clicked.connect(parent.open_camera)
        self.button_open_file = QPushButton('Видео файл')
        self.button_open_file.clicked.connect(parent.open_file)

        layout = QVBoxLayout()
        layout.addWidget(self.label)
        layout.addWidget(self.button_open_camera)
        layout.addWidget(self.button_open_file)
        self.setLayout(layout)



class VideoWidget(QWidget):

    def __init__(self):
        super().__init__()
        self.initUI()

        self.thread_camera = ThreadCamera()
        self.thread_video = ThreadVideoFile("videoplayback.mp4")



    @Slot(QImage)
    def setImage (self, image):
        self.label.setPixmap(QPixmap.fromImage(image))

    def initUI(self):
        self.setFixedSize(640, 480)
        self.label = QLabel(self)
        self.label.setText("Загрузите видео")
        self.label.setAlignment(Qt.AlignCenter)
        self.label.installEventFilter(self)
        self.layout = QVBoxLayout(self)
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.layout.addWidget(self.label)
        self.setLayout(self.layout)


    def eventFilter(self, obj, event):
        # Только фильтровать событие label, переписать его поведение,
        # другие события будут проигнорированы
        if obj == self.label:
            # здесь отфильтруйте событие mouse и перепишите его поведение
            if event.type() == QEvent.MouseButtonPress:

                if event.buttons() == Qt.LeftButton:
                    self.dialog = Dialog(self)
                    self.dialog.exec_()
        return False
    def open_file(self):
        if self.thread_video.isRunning() is True:
            self.thread_video.set_status(False)
            self.thread_video.captureRelease()

        self.dialog.hide()
        self.thread_camera.setStatus(False)
        self.thread_camera.captureRelease()

        self.label.setText("Загрузите видео")

        fileDialog = QFileDialog(self)
        supportedMimeTypes = ["video", "*.*"]
        fileDialog.setMimeTypeFilters(supportedMimeTypes)
        moviesLocation = QStandardPaths.writableLocation(QStandardPaths.MoviesLocation)
        fileDialog.setDirectory(moviesLocation)

        if fileDialog.exec_() == QDialog.Accepted:
            self.file = fileDialog.selectedUrls()[0].toDisplayString()
            self.thread_video = ThreadVideoFile(self.file)
        self.thread_video.changePixmap.connect(self.setImage)
        self.thread_video.start()


    def open_camera(self):
        self.dialog.hide()

        self.thread_video.set_status(False)
        self.thread_video.captureRelease()
        self.label.setText("Загрузите видео")

        self.thread_camera = ThreadCamera()
        self.thread_camera.changePixmap.connect(self.setImage)
        self.thread_camera.start()


def main():
    app = QApplication([])
    video_widget = VideoWidget()
    video_widget.show()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()


Дальше сами)
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы