개발 · 컴퓨터공학 / / 2024. 6. 14. 09:02

OOTDiffusion을 이용한 PyQT 애플리케이션 만들기 (파이썬 GUI 프로그램, QT 애플리케이션, virtual try on, 가상피팅, diffusion model)

728x90
반응형

PyQT란

pyQT는 대표적인 크로스 플랫폼 프레임워크인 QT를 python 상에서 이용할 수 있도록 바인딩 해놓은 것이다

가상피팅 오픈소스 OOTDiffusion을 가지고 pyQT로 응용 프로그램을 개발해볼 것이다

 

 

 

OOTDiffusion 코드를 받아서 환경 세팅하고 실행하는 포스팅은 정리해두었기에 추후 따로 준비해보도록 하겠다

 

OOTDiffusion 실행 코드

python run_ootd.py --model_path ./examples/model/model_1.png --cloth_path ./examples/garment/00055_00.jpg --scale 2.0 --sample 1

위 코드를 이용하면 model 이미지와 garment 이미지를 가지고 가상 피팅을 한 결과 1개의 샘플을 뽑을 수 있다

본래 sample option이 4로 설정되어있지만 메모리의 한계로 한 번에 1개까지가 정상적으로 실행된다

 

run_ootd.py가 main함수라고 보면 되는데 여기에 pyQT를 설치해서 애플리케이션으로 만드는 방법을 구상해보자

PyQT 설치

일단 프로젝트에 pyqt를 설치해보자

pip install PyQt5

PyQT로 GUI 구성하기

import sys
import os
import subprocess
from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QPushButton, QLabel, QFileDialog, QLineEdit, QVBoxLayout, QWidget
)
from PyQt5.QtCore import Qt

class OotdApp(QMainWindow):
    def __init__(self):
        super().__init__()

        self.model_path = ""
        self.cloth_path = ""
        self.initUI()

    def initUI(self):
        self.setWindowTitle('OOTDiffusion')

        self.model_label = QLabel('Model Path: Not Selected', self)
        self.model_label.setAlignment(Qt.AlignCenter)

        self.cloth_label = QLabel('Cloth Path: Not Selected', self)
        self.cloth_label.setAlignment(Qt.AlignCenter)

        self.scale_label = QLabel('Scale:', self)
        self.scale_input = QLineEdit(self)
        self.scale_input.setText("2.0")

        self.sample_label = QLabel('Sample:', self)
        self.sample_input = QLineEdit(self)
        self.sample_input.setText("1")

        self.model_button = QPushButton('Select Model', self)
        self.model_button.clicked.connect(self.select_model)

        self.cloth_button = QPushButton('Select Cloth', self)
        self.cloth_button.clicked.connect(self.select_cloth)

        self.run_button = QPushButton('Run', self)
        self.run_button.clicked.connect(self.run_command)

        layout = QVBoxLayout()
        layout.addWidget(self.model_label)
        layout.addWidget(self.model_button)
        layout.addWidget(self.cloth_label)
        layout.addWidget(self.cloth_button)
        layout.addWidget(self.scale_label)
        layout.addWidget(self.scale_input)
        layout.addWidget(self.sample_label)
        layout.addWidget(self.sample_input)
        layout.addWidget(self.run_button)

        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)

    def select_model(self):
        options = QFileDialog.Options()
        file_path, _ = QFileDialog.getOpenFileName(self, "Select Model File", "", "All Files (*);;PNG Files (*.png)", options=options)
        if file_path:
            self.model_path = file_path
            self.model_label.setText(f'Model Path: {file_path}')

    def select_cloth(self):
        options = QFileDialog.Options()
        file_path, _ = QFileDialog.getOpenFileName(self, "Select Cloth File", "", "All Files (*);;JPEG Files (*.jpg; *.jpeg)", options=options)
        if file_path:
            self.cloth_path = file_path
            self.cloth_label.setText(f'Cloth Path: {file_path}')

    def run_command(self):
        scale = self.scale_input.text()
        sample = self.sample_input.text()

        if not self.model_path or not self.cloth_path:
            print("Please select both model and cloth files.")
            return

        command = f'python run_ootd.py --model_path {self.model_path} --cloth_path {self.cloth_path} --scale {scale} --sample {sample}'
        print(f'Executing: {command}')
        
        process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        out, err = process.communicate()
        
        if process.returncode == 0:
            print("Command executed successfully")
            print(out.decode('utf-8'))
        else:
            print("Error executing command")
            print(err.decode('utf-8'))

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = OotdApp()
    ex.show()
    sys.exit(app.exec_())

위 코드로 대강 GUI를 구성해보았다 

위 코드를 main.py로 스크립트를 생성해서 실행하면 pyQT가 잘 작동한다

 

model과 garment 이미지를 넣을 수 있도록 버튼을 누르면 파일탐색기에서 파일을 가져오는 기능이 구현되어있다

 

여기에 Run 버튼을 누르면 run_ootd.py 스크립트를 실행하도록 하면 될 것 같다

그리고 실행하여 나온 결과물 이미지를 QT 상으로 띄워주면 기능 측면에서는 충분하다

 

model과 cloth 이미지를 선택해서 넣고, sample 개수를 1로 설정해서 run을 눌러보았다

상당히 오랜 시간동안 프로그램이 응답이 없다 

기다림...

응답이 완료되고 로그를 보니 성공한 듯 하다

 

output image 폴더를 가보니 mask.jpg out_hd_o.png 두 이미지가 잘 생성되었다

이제 pyQT에 추가할 기능

전반적인 프로그램의 흐름은 완성되었고, 먼저 input image 두 개와 output image가 프로그램 상에서 직관적으로 보이게 레이아웃을 수정하고 이미지를 띄워주자

 

그리고 Run으로 OOTDiffusion을 실행하는 동안 프로그램이 완전히 정지한 상태가 된다

그러지 말고 비동기적으로 처리하고 progress bar를 띄울 수 있게 하면 좋을 것 같다

 

결과

import sys
import os
import subprocess
from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QPushButton, QLabel, QFileDialog, QLineEdit, QVBoxLayout, QHBoxLayout, QWidget,
    QGridLayout
)
from PyQt5.QtGui import QPixmap
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer


class OotdApp(QMainWindow):
    def __init__(self):
        super().__init__()

        self.model_path = ""
        self.cloth_path = ""
        self.output_image_path = "D:/MediaAILab/Multimedia/OOTDiffusion/run/images_output/out_hd_0.png"
        self.initUI()

    def initUI(self):
        self.setWindowTitle('Virtual Fitting')

        # Model and Cloth Labels
        self.model_label = QLabel('Model Path: Not Selected', self)
        self.model_label.setAlignment(Qt.AlignCenter)

        self.cloth_label = QLabel('Cloth Path: Not Selected', self)
        self.cloth_label.setAlignment(Qt.AlignCenter)

        # Scale and Sample Inputs
        self.scale_label = QLabel('Scale:', self)
        self.scale_input = QLineEdit(self)
        self.scale_input.setText("2.0")

        self.sample_label = QLabel('Sample:', self)
        self.sample_input = QLineEdit(self)
        self.sample_input.setText("1")

        # Buttons
        self.model_button = QPushButton('Select Model', self)
        self.model_button.clicked.connect(self.select_model)

        self.cloth_button = QPushButton('Select Cloth', self)
        self.cloth_button.clicked.connect(self.select_cloth)

        self.run_button = QPushButton('Run', self)
        self.run_button.clicked.connect(self.run_command)

        # Image Labels for displaying input and output images
        self.input_model_image_label = QLabel('Model Image', self)
        self.input_model_image_label.setAlignment(Qt.AlignCenter)
        self.input_model_image = QLabel(self)
        self.input_model_image.setAlignment(Qt.AlignCenter)
        self.input_model_image.setFixedSize(300, 300)  # 이미지 크기 조정
        self.input_model_image.setStyleSheet("border: 1px solid black;")  # 경계선 추가
        self.input_model_image.setScaledContents(True)  # 이미지가 QLabel 크기에 맞추어 조정되도록 설정

        self.input_cloth_image_label = QLabel('Cloth Image', self)
        self.input_cloth_image_label.setAlignment(Qt.AlignCenter)
        self.input_cloth_image = QLabel(self)
        self.input_cloth_image.setAlignment(Qt.AlignCenter)
        self.input_cloth_image.setFixedSize(300, 300)  # 이미지 크기 조정
        self.input_cloth_image.setStyleSheet("border: 1px solid black;")  # 경계선 추가
        self.input_cloth_image.setScaledContents(True)  # 이미지가 QLabel 크기에 맞추어 조정되도록 설정

        self.output_image_label = QLabel('Output Image', self)
        self.output_image_label.setAlignment(Qt.AlignCenter)
        self.output_image = QLabel(self)
        self.output_image.setAlignment(Qt.AlignCenter)
        self.output_image.setFixedSize(300, 300)  # 이미지 크기 조정
        self.output_image.setStyleSheet("border: 1px solid black;")  # 경계선 추가
        self.output_image.setScaledContents(True)  # 이미지가 QLabel 크기에 맞추어 조정되도록 설정

        # Loading Label
        self.loading_label = QLabel('', self)
        self.loading_label.setAlignment(Qt.AlignCenter)

        # Layouts
        grid_layout = QGridLayout()

        # Add images to the grid layout
        grid_layout.addWidget(self.input_model_image_label, 0, 0)
        grid_layout.addWidget(self.input_model_image, 1, 0)
        grid_layout.addWidget(self.input_cloth_image_label, 0, 1)
        grid_layout.addWidget(self.input_cloth_image, 1, 1)
        grid_layout.addWidget(self.output_image_label, 0, 2)
        grid_layout.addWidget(self.output_image, 1, 2)

        # Add controls to the grid layout
        grid_layout.addWidget(self.model_button, 2, 0)
        grid_layout.addWidget(self.cloth_button, 2, 1)
        grid_layout.addWidget(self.run_button, 2, 2)
        grid_layout.addWidget(self.model_label, 3, 0)
        grid_layout.addWidget(self.cloth_label, 3, 1)
        grid_layout.addWidget(self.scale_label, 4, 0)
        grid_layout.addWidget(self.scale_input, 4, 1)
        grid_layout.addWidget(self.sample_label, 5, 0)
        grid_layout.addWidget(self.sample_input, 5, 1)
        grid_layout.addWidget(self.loading_label, 4, 2, 2, 1)

        container = QWidget()
        container.setLayout(grid_layout)
        self.setCentralWidget(container)

        # Loading animation timer
        self.loading_timer = QTimer(self)
        self.loading_timer.timeout.connect(self.update_loading_text)
        self.loading_dots = 0

    def select_model(self):
        options = QFileDialog.Options()
        file_path, _ = QFileDialog.getOpenFileName(self, "Select Model File", "", "All Files (*);;PNG Files (*.png)",
                                                   options=options)
        if file_path:
            self.model_path = file_path
            self.model_label.setText(f'Model Path: {file_path}')
            pixmap = QPixmap(file_path)
            self.input_model_image.setPixmap(pixmap)

    def select_cloth(self):
        options = QFileDialog.Options()
        file_path, _ = QFileDialog.getOpenFileName(self, "Select Cloth File", "",
                                                   "All Files (*);;JPEG Files (*.jpg; *.jpeg)", options=options)
        if file_path:
            self.cloth_path = file_path
            self.cloth_label.setText(f'Cloth Path: {file_path}')
            pixmap = QPixmap(file_path)
            self.input_cloth_image.setPixmap(pixmap)

    def run_command(self):
        scale = self.scale_input.text()
        sample = self.sample_input.text()

        if not self.model_path or not self.cloth_path:
            print("Please select both model and cloth files.")
            return

        command = f'python run_ootd.py --model_path {self.model_path} --cloth_path {self.cloth_path} --scale {scale} --sample {sample}'
        print(f'Executing: {command}')

        self.run_button.setEnabled(False)  # Run 버튼 비활성화
        self.loading_label.setText('Loading.')
        self.loading_label.setStyleSheet('color: black')  # 초기 색상 설정
        self.loading_timer.start(1000)  # 1초마다 타이머 업데이트
        self.thread = CommandThread(command)
        self.thread.finished.connect(self.command_finished)
        self.thread.start()

    def update_loading_text(self):
        self.loading_dots = (self.loading_dots % 3) + 1
        self.loading_label.setText('Loading' + '.' * self.loading_dots)

    def command_finished(self, success):
        self.run_button.setEnabled(True)  # Run 버튼 다시 활성화
        self.loading_timer.stop()
        if success:
            print("Command executed successfully")
            if os.path.exists(self.output_image_path):
                pixmap = QPixmap(self.output_image_path)
                self.output_image.setPixmap(pixmap)
            self.loading_label.setText('Complete!')
            self.loading_label.setStyleSheet('color: green')
        else:
            print("Error executing command")
            self.loading_label.setText('Error occurred')
            self.loading_label.setStyleSheet('color: red')


class CommandThread(QThread):
    progress_changed = pyqtSignal(int)
    finished = pyqtSignal(bool)

    def __init__(self, command):
        super().__init__()
        self.command = command

    def run(self):
        process = subprocess.Popen(self.command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
        total_lines = 100  # 임의의 총 라인 수. 실제로는 적절한 값으로 설정해야 합니다.
        current_line = 0

        while True:
            output = process.stdout.readline()
            if output == '' and process.poll() is not None:
                break
            if output:
                print(output.strip())  # 콘솔에 출력
                if "Progress" in output:
                    try:
                        progress = int(output.split("Progress")[1].strip().split('%')[0])
                        self.progress_changed.emit(progress)
                    except ValueError:
                        pass
                current_line += 1
                progress = int((current_line / total_lines) * 100)
                self.progress_changed.emit(progress)

        process.stdout.close()
        process.stderr.close()
        success = process.returncode == 0
        self.finished.emit(success)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = OotdApp()
    ex.show()
    sys.exit(app.exec_())

기능들을 추가한 결과 위와 같이 레이아웃을 만들었다

 

진행도의 경우 퍼센트로 표시하기에는 프로세스가 얼마나 진행되었는지를 알 방법이 없어서 

loading 과 complete로만 표시하는 것으로 수정했다 .

728x90
반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유