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로만 표시하는 것으로 수정했다 .