주간 다이어리 - 11주차
활동 기록
팀 활동
- 5월 14일(화) 10:00 ~ 21:03 (오프라인 개발)
- 5월 15일(수) 10:00 ~ 21:34 (오프라인 개발)
- 5월 16일(목) 12:00 ~ 21:02 (오프라인 개발)
- 5월 17일(금) 10:00 ~ 17:30 (오프라인 개발)
→ 총 37시간 진행
개별 활동
- 유재휘 - 05.13(월) 10:40 ~ (활동시간표 참고)
- 이민석 - 05.13(월) 18:39 ~ 19:33
진행 상황
1) Pyqt5 설치 관련
에러메시지
pyqt5 설치가 안된 상태로 관련 프로그램 코드를 실행하면 아래와 같은 메시지가 뜸
qt.qpa.plugin: Could not find the Qt platform plugin "windows" in ""
This application failed to start because no Qt platform plugin could be initialized.
Reinstalling the application may fix this problem.
생긴 원인
환경변수에 QT 플러그인 경로가 없어서 생긴 문제
해결 방법
1) 시스템 환경 변수 추가 → X
[기존 파이썬 설치 경로]\Lib\site-packages\PyQt5\Qt\plugins\platforms
2) 터미널에 아래 명령어 순서대로 입력 → X
PyGLM PySide2 pyopengl 를 재설치
pip uninstall PyGLM PySide2 pyopengl
pip install PyGLM PySide2 pyopengl
pyqt5관련 모듈 설치
pip install pyqt5
pip install pyqt5-tools
3) 파이썬 완전 삭제 후 재설치 → 해결 완료
참고글 : Could not load the Qt platform plugin 에러 해결 :: 내 삶 을 로 깅 하 기 (tistory.com)
4) 추가
필요에 따라 터미널에 아래 명령어를 입력
pip install pyside6
2)녹음 기능
녹음 기능을 사용하기 위한 라이브러리 설치
sounddevice 라이브러리 설치
pip install sounddevice
- 녹음 및 .wav 파일로 저장, thread를 사용해서 따로 돌리는 형식
-
따로 record.py 파일을 만들어 저장 메인 프로그램에서는 해당파일 import하여 사용
import queue, os, threading
import time
import sounddevice as sd
import soundfile as sf
from scipy.io.wavfile import write
SAMPLERATE = 16000
CHANNELS = 1
q = queue.Queue()
recorder = False
recording = False
def complicated_record():
filename = "./record.wav"
# 파일이 존재하지 않으면 새로 생성
with sf.SoundFile(filename, mode='w', samplerate=SAMPLERATE, subtype='PCM_16', channels=CHANNELS) as file:
with sd.InputStream(samplerate=SAMPLERATE, dtype='int16', channels=CHANNELS, callback=complicated_save):
while recording:
file.write(q.get())
def complicated_save(indata,frames,time,status):
q.put(indata.copy())
def start():
global recorder
global recording
recording = True
recorder = threading.Thread(target=complicated_record)
print("start recording")
recorder.start()
def stop():
global recorder
global recording
recording = False
recorder.join()
print("record stop")
- 메인프로그램 연동
-
위에서 제작한 record.py 파일 import 구문 추가
import record
메인 프로그램에서 버튼을 사용하여 녹음 및 녹음 중지 추가
def start_record(self): #녹음 시작
self.button_startrecord.hide()
self.button_stoprecord.show()
record.start()
def uiresult(self): #녹음 중지 및 결과 창으로 이동
record.stop()
3) Siamese network 테스트 코드 개선
개선 사항
- 3차 학습진행 후, 테스트 과정에서 이미지만으로는 해당 파일의 원본을 확인하기 어렵다는 것을 느꼈다. 이를 개선하기 위하여 테스트 코드에 결과 이미지 및 유사도와 함께 이미지들의 파일명까지 출력이 되도록 코드를 변경하였다.
- Custom Dataset의 getitem 메서드를 수정하여 dataloader를 이용해 데이터를 가져올 때 파일의 경로까지 가져오도록 하였다.
Custom Dataset 정의 코드
class SiameseNetworkDataset(Dataset):
def __init__(self,imageFolderDataset,transform=None,should_invert=False):
self.imageFolderDataset = imageFolderDataset
self.transform = transform
self.should_invert = should_invert
def __getitem__(self,index):
img0_tuple = random.choice(self.imageFolderDataset.imgs)
#we need to make sure approx 50% of images are in the same class
should_get_same_class = random.randint(0,1)
if should_get_same_class:
while True:
#keep looping till the same class image is found
img1_tuple = random.choice(self.imageFolderDataset.imgs)
if img0_tuple[1]==img1_tuple[1]:
break
else:
while True:
#keep looping till a different class image is found
img1_tuple = random.choice(self.imageFolderDataset.imgs)
if img0_tuple[1] !=img1_tuple[1]:
break
img0_path = img0_tuple[0]
img1_path = img1_tuple[0]
img0 = Image.open(img0_tuple[0])
img1 = Image.open(img1_tuple[0])
if self.should_invert:
img0 = PIL.ImageOps.invert(img0)
img1 = PIL.ImageOps.invert(img1)
if self.transform is not None:
img0 = self.transform(img0)
img1 = self.transform(img1)
return img0, img1 , torch.from_numpy(np.array([int(img1_tuple[1]!=img0_tuple[1])],dtype=np.float32)), img0_path, img1_path
# 두 이미지가 서로 다른 클래스면 1
# 두 이미지가 서로 같은 클래스면 0
def __len__(self):
return len(self.imageFolderDataset.imgs)
테스트 코드
folder_dataset_test = dset.ImageFolder(root=Config.testing_dir)
siamese_dataset = SiameseNetworkDataset(imageFolderDataset=folder_dataset_test,
transform=transforms.Compose([transforms.Resize((99,250)),
transforms.ToTensor()
])
,should_invert=False)
test_dataloader = DataLoader(siamese_dataset,num_workers=1,batch_size=1,shuffle=True) # 1장씩 test
dataiter = iter(test_dataloader)
for i in range(10):
x0,x1,label1,x0_path,x1_path = next(dataiter)
concatenated = torch.cat((x0,x1),0)
output1,output2 = net(Variable(x0).cuda(),Variable(x1).cuda())
euclidean_distance = F.pairwise_distance(output1, output2)
imshow(torchvision.utils.make_grid(concatenated),'isNotSame : {:.0f}\nDissimilarity: {:.2f}'.format(label1.item(),euclidean_distance.item()))
print(f"left img's name : {os.path.basename(str(x0_path))}\nright img's name : {os.path.basename(str(x1_path))}")
4) 기준 데이터와 녹음데이터 간의 유사도 평가 테스트
- 남/여 음성 특성 차이
-
같은 발음이지만 성별이 다른 경우, 결과의 차이 발생여부에 대해 테스트를 진행하였다. 그 결과 발음은 같아도 성별이 다르면 유사도가 많이 감소하는 것으로 나타났다. 따라서 처음 예상했던 데로 성별에 따라 결과값에 영향을 주기 때문에 사용자의 성별에 따라 기준 데이터의 생성방법을(남성,여성) 다르게 해야 할 것이다.
-
- 유사도 평가
- 여성 녹음 데이터에 대해 여성 기준데이터를 생성하여 평가를 진행한 경우, 0.78이라는 유사도 값이 나왔다.
-
여성 녹음 데이터에 대해 남성 기준데이터를 생성하여 평가를 진행한 경우, 위의 결과보다 유사도가 0.52만큼 낮게 나왔다.
→ 유사도의 범위가 0.00 ~ 2.xx (0이 유사도가 제일 높음) 인 것을 감안하면 0.78도 충분히 높은 유사도에 속하지만 아직까지 기준이 명확하지 않아 원본을 들어보았을 때 해당 발음이 몇점인지 평가하기가 힘들었다. 따라서 정확도향상을 위한 추가학습을 진행하기전에 우선 유사도 결과를 계산하여 0~100점으로 나타내어 주는 부분의 개발을 우선적으로 진행할 것이다.
5) 유사도 계산 코드 추가
개선 사항
- 기존의 코드에서는 유사도의 측정 결과가 비슷할수록 0에 가깝게 나타났다. 값의 범위가 0.xx~2.xx이기 때문에 발음이 얼마나 정확한지 직관적으로 판단하기 어렵다는 문제가 있었다. 이를 보완하기 위하여 기존의 유사도 측정값 범위를 0~100으로 변경시키는 함수를 작성하였다.
- 여러 테스트 결과 유사도가 2보다 큰 경우는 대부분 기준데이터와 발음이 크게 달랐기 때문에 2보다 큰 경우는 모두 score를 모두 0으로 처리하였다. 그 외의 부분에 대해서는 유사도 1.00이 50점이 되는 것을 기준으로 잡아 코딩을 하였다.
- (현재 테스트를 통하여 정확도의 개선이 이루어지고 있기 때문에 유사도 측정 값과 실제 정확도 간의 mapping 값이 변경될 가능성이 있다. 따라서 기준이 되는 점수의 범위는 정확도 개선이 진행됨에 따라 수시로 변경 될 예정이다.)
getScore 정의코드
def getScore(dissimilarity):
if dissimilarity >= 2.0:
score = 0
else:
score = 100 - dissimilarity*50
score = round(score)
return scor
6) 메인 프로그램에 탑재될 모델 추론 코드 제작
개선 사항
- 기존에 사용하던 test 코드는 pytorch에서 제공하는 dataset과 dataloader를 활용하여 여러 장을 동시에 테스트 하고, 결과로 유사도 수치값, 추론의 정답여부, 파일 명 등 디버그에 특화된 코드였다.
- 실제 메인프로그램에서는 평가의 기준이 되는 정답데이터와 사용자의 녹음데이터를 mel-spectrogram으로 변환시켜서(음성데이터 → 이미지로 변환) 2장의 이미지에 대해서만 추론을 진행하기 때문에 기존의 test코드를 그대로 사용하기에는 부적합하였다. 따라서 메인프로그램에서의 동작에 특화된 추론코드를 작성하였고, 전체 코드는 다음과 같다.
모델 추론 코드
import torchvision.transforms as transforms
from PIL import Image
import torch
import torch.nn as nn
import torch.nn.functional as F
def getScore(dissimilarity):
if dissimilarity >= 2.0:
score = 0
else:
score = 100 - dissimilarity*50
score = round(score)
return score
class SiameseNetwork(nn.Module):
def __init__(self):
super(SiameseNetwork, self).__init__()
self.cnn1 = nn.Sequential(
nn.ReflectionPad2d(1),
nn.Conv2d(3, 4, kernel_size=3),
nn.ReLU(inplace=True),
nn.BatchNorm2d(4),
nn.ReflectionPad2d(1),
nn.Conv2d(4, 8, kernel_size=3),
nn.ReLU(inplace=True),
nn.BatchNorm2d(8),
nn.ReflectionPad2d(1),
nn.Conv2d(8, 8, kernel_size=3),
nn.ReLU(inplace=True),
nn.BatchNorm2d(8),
)
self.fc1 = nn.Sequential(
nn.Linear(8*99*250, 500),
nn.ReLU(inplace=True),
nn.Linear(500, 500),
nn.ReLU(inplace=True),
nn.Linear(500, 5))
def forward_once(self, x):
output = self.cnn1(x)
output = output.view(output.size()[0], -1) # flatten
output = self.fc1(output)
return output
def forward(self, input1, input2):
output1 = self.forward_once(input1)
output2 = self.forward_once(input2)
return output1, output2
device = "cuda" if torch.cuda.is_available() else "cpu"
# 모델 이름 경로
model = torch.load("./model/siamese_net_v4.pt", map_location=device)
# 비교 할 이미지들의 경로
x0 = Image.open("kr/Mel_spectrum_VAD_KsponSpeech_000098.jpg")
x1 = Image.open("data/testing/korean/man.jpg")
convert_tensor = transforms.Compose([transforms.Resize((99,250)),transforms.ToTensor()])
x0 = convert_tensor(x0).unsqueeze(0)
x1 = convert_tensor(x1).unsqueeze(0)
print(x0.shape)
print(x1.shape)
output1, output2 = model(x0, x1)
euclidean_distance = F.pairwise_distance(output1, output2)
print(f"score : {getScore(euclidean_distance.item())}")
- 해당 추론 코드는 2장의 mel-spectrogram(음성데이터의 특징)을 입력으로 받아 두 데이터간의 유사도를 0~100점으로 출력해준다.
→ 이로써 Siamese Network 를 메인 프로그램에 탑재 및 적용시키기 모든 준비가 완료되었다. AI 영역에서 남은 내용은 모델의 정확도 향상부분 뿐이므로 남은 기간동안 모델의 정확도 향상에 집중을 할 예정이다.
7) 메인프로그램 제작
에러상황 : 소음측정 결과를 나타내는화면에서 여러번 반복할 경우 텍스트가 겹쳐서 나오는 현상이 나타남
→ 기존 dialog내부에서만 Qlabel 사용하던것을 전체적인 코드로 넘겨 다루기로 하였음.
self.label.setText()를 사용하여 업데이트가 되도록 변경→ 해결완료
현재 제작된 전체적인 메인 프로그램 코드
import sys
from PyQt5 import uic
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import time
import math
import record
import torchvision
import torchvision.datasets as dset
import torchvision.transforms as transforms
from torch.utils.data import DataLoader,Dataset
import matplotlib.pyplot as plt
import torchvision.utils
import numpy as np
import random
from PIL import Image
import torch
from torch.autograd import Variable
import PIL.ImageOps
import torch.nn as nn
from torch import optim
import torch.nn.functional as F
import button_rc
form_class = uic.loadUiType("./sub.ui")[0]
class WindowClass(QMainWindow, form_class):
def __init__(self):
super().__init__()
self.setupUi(self)
self.checknoise.setHidden(True)
self.selectsexual.setHidden(True)
self.resultnoise.setHidden(True)
self.result.setHidden(True)
self.button_selectsexual.clicked.connect(self.uiselectsexual)
self.button_startchecknoise.clicked.connect(self.uichecknoise)
self.button_resultnoise.clicked.connect(self.uiresultnoise)
self.button_resultnoise.clicked.connect(self.show_noise_result)
self.button_startrecord.clicked.connect(self.start_record)
self.button_stoprecord.clicked.connect(self.uiresult)
self.button_backtoselectsexual.clicked.connect(self.uiselectsexual)
self.button_backtochecknoise.clicked.connect(self.uichecknoise)
self.button_backtomain.clicked.connect(self.uimain)
self.button_rerecord.clicked.connect(self.uiresultnoise)
self.button_exit.clicked.connect(self.close)
self.noise = None
self.dialog = QDialog()
self.buttongroup_sexual = QButtonGroup(self)
self.buttongroup_sexual.setExclusive(True)
self.buttongroup_sexual.addButton(self.check_man,1)
self.buttongroup_sexual.addButton(self.check_woman,2)
def uimain(self):
self.selectsexual.hide()
self.checknoise.hide()
self.resultnoise.hide()
self.result.hide()
self.mainwindow.show()
def uiselectsexual(self):
self.selectsexual.show()
self.checknoise.hide()
self.resultnoise.hide()
self.result.hide()
self.mainwindow.hide()
def uichecknoise(self):
self.selectsexual.hide()
self.checknoise.show()
self.resultnoise.hide()
self.result.hide()
self.mainwindow.hide()
def read_sensor_data(self): # 사운드 센서값을 불러오는 함수
while True:
r = self.spi.xfer2([1, (8 + 0) << 4, 0])
adc_out = ((r[1] & 3) << 8) + r[2]
analog_value = adc_out
if analog_value <= 0:
analog_value = 1
db_value = round(20 * math.log10(analog_value), 1)
time.sleep(0.5)
return db_value
def uiresultnoise(self):
self.selectsexual.hide()
self.checknoise.hide()
self.resultnoise.show()
self.button_stoprecord.hide()
self.button_startrecord.show()
self.select_word.setPlainText(random(list))
self.result.hide()
self.mainwindow.hide()
def start_record(self):
self.button_startrecord.hide()
self.button_stoprecord.show()
record.start()
def uiresult(self):
self.selectsexual.hide()
self.checknoise.hide()
self.resultnoise.hide()
record.stop()
self.result.show()
self.mainwindow.hide()
def show_noise_result(self):
self.dialog.setWindowTitle("Dialog")
self.dialog.setWindowModality(Qt.ApplicationModal)
self.dialog.resize(300, 200)
db_value = 5 # 사운드센서 값 불러옴
self.noise = str(db_value) + "db"
noiselabel = QLabel(self.dialog)
noiselabel.move(100, 100)
noiselabel.setText(self.noise)
if db_value > 80:
noiselabel.setStyleSheet("COLOR : red")
elif db_value <= 80 and db_value > 60:
noiselabel.setStyleSheet("COLOR : yellow")
else:
noiselabel.setStyleSheet("COLOR : green")
self.dialog.setWindowTitle("소음측정결과")
self.dialog.show()
#list = ['안녕하세요', '바가지','도깨비','고구마','누룽지','주전자']
class Config():
testing_dir = "./testing"
def imshow(img, text=None, should_save=False):
npimg = img.numpy()
plt.axis("off")
if text:
plt.text(75, 8, text, style='italic', fontweight='bold',
bbox={'facecolor': 'white', 'alpha': 0.8, 'pad': 10})
plt.imshow(np.transpose(npimg, (1, 2, 0)))
plt.show()
def show_plot(iteration, loss):
plt.plot(iteration, loss)
plt.show()
class SiameseNetworkDataset(Dataset):
def __init__(self, imageFolderDataset, transform=None, should_invert=False):
self.imageFolderDataset = imageFolderDataset
self.transform = transform
self.should_invert = should_invert
def __getitem__(self, index):
img0_tuple = random.choice(self.imageFolderDataset.imgs)
should_get_same_class = random.randint(0, 1)
if should_get_same_class:
while True:
img1_tuple = random.choice(self.imageFolderDataset.imgs)
if img0_tuple[1] == img1_tuple[1]:
break
else:
while True:
img1_tuple = random.choice(self.imageFolderDataset.imgs)
if img0_tuple[1] != img1_tuple[1]:
break
img0 = Image.open(img0_tuple[0])
img1 = Image.open(img1_tuple[0])
if self.should_invert:
img0 = PIL.ImageOps.invert(img0)
img1 = PIL.ImageOps.invert(img1)
if self.transform is not None:
img0 = self.transform(img0)
img1 = self.transform(img1)
return img0, img1, torch.from_numpy(np.array([int(img1_tuple[1] != img0_tuple[1])], dtype=np.float32))
# 두 이미지가 서로 다른 클래스면 1
# 두 이미지가 서로 같은 클래스면 0
def __len__(self):
return len(self.imageFolderDataset.imgs)
class SiameseNetwork(nn.Module):
def __init__(self):
super(SiameseNetwork, self).__init__()
self.cnn1 = nn.Sequential(
nn.ReflectionPad2d(1), # 가장자리의 특징들까지 고려
nn.Conv2d(3, 4, kernel_size=3),
nn.ReLU(inplace=True),
nn.BatchNorm2d(4),
nn.ReflectionPad2d(1),
nn.Conv2d(4, 8, kernel_size=3),
nn.ReLU(inplace=True),
nn.BatchNorm2d(8),
nn.ReflectionPad2d(1),
nn.Conv2d(8, 8, kernel_size=3),
nn.ReLU(inplace=True),
nn.BatchNorm2d(8),
)
self.fc1 = nn.Sequential(
nn.Linear(8 * 99 * 250, 500),
nn.ReLU(inplace=True),
nn.Linear(500, 500),
nn.ReLU(inplace=True),
nn.Linear(500, 5))
def forward_once(self, x):
output = self.cnn1(x)
output = output.view(output.size()[0], -1) # flatten
output = self.fc1(output)
return output
def forward(self, input1, input2):
output1 = self.forward_once(input1)
output2 = self.forward_once(input2)
return output1, output2
device = "cuda" if torch.cuda.is_available() else "cpu"
model = torch.load("siamese_net_v4.pt", map_location=device)
print(model)
folder_dataset_test = dset.ImageFolder(root=Config.testing_dir)
siamese_dataset = SiameseNetworkDataset(imageFolderDataset=folder_dataset_test,
transform=transforms.Compose([transforms.Resize((99, 250)),
transforms.ToTensor()
])
, should_invert=False)
test_dataloader = DataLoader(siamese_dataset, num_workers=0, batch_size=1, shuffle=True) # 1장씩 test
dataiter = iter(test_dataloader)
# x0,x1,label1 = next(dataiter) # test의 기준이 되는 img.
# 0 : same class , 1 : other class
for i in range(10):
x0, x1, label1 = next(dataiter)
concatenated = torch.cat((x0, x1), 0)
# output1,output2 = model(Variable(x0).cuda(),Variable(x1).cuda())
output1, output2 = model(Variable(x0), Variable(x1))
euclidean_distance = F.pairwise_distance(output1, output2)
# imshow(torchvision.utils.make_grid(concatenated),
# 'isNotSame : {:.0f}\nDissimilarity: {:.2f}'.format(label1.item(), euclidean_distance.item()))
if __name__ == '__main__':
app = QApplication(sys.argv)
ui = WindowClass()
ui.show()
exit(app.exec_())
현재 개발 완료 기능 : 성별 선택, 소음 측정, 소음측정 결과에 따른 결과 색 조정, 녹음 기능, siamesenetwork를 활용한 유사도 판별 기능, tts
8) 메인 프로그램에 기능 추가 - VAD, MEL 알고리즘 적용 기능
추가한 기능
- 사용자가 녹음한 음원파일에 VAD, MEL 알고리즘 자동 적용
- MEL이 적용된 두 개의 음성데이터인 .jpg 이미지를 서로 비교하여 유사도 결과 위젯에 출력
추가해야 할 기능
- TTS 음성데이터 적용 파트
- 학습할 단어 선택 리스트 추가 (스크롤 형식…요소 선택)
수정한 sub.py 코드부분
‘목차6)’ 의 모델 추론 코드를 이용하여 녹음 종료 후에 바로 유사도를 측정하고, 이를 결과로 출력
# ----------------------------------------------------------------
# 나머지 코드 내용...
# 유사도 측정 함수
def similar_test(self):
# 측정...
device = "cuda" if torch.cuda.is_available() else "cpu"
# 모델 이름 경로
model = torch.load("siamese_net_v4.pt", map_location=device)
# 비교하려는 이미지(.jpg)들의 경로
# x0 : 사용자가 녹음한 음성데이터의 Mel 이미지
# x1 : 기준이 되는 TTS 음성데이터의 Mel 이미지
x0 = Image.open("Mel_record_after_vad.jpg")
x1 = Image.open("data/testing/korean/Mel_spectrum_VAD_KsponSpeech_000091.jpg")
convert_tensor = transforms.Compose([transforms.Resize((99,250)),transforms.ToTensor()])
x0 = convert_tensor(x0).unsqueeze(0)
x1 = convert_tensor(x1).unsqueeze(0)
print(x0.shape)
print(x1.shape)
output1, output2 = model(x0, x1)
euclidean_distance = F.pairwise_distance(output1, output2)
final_similar_score = getScore(euclidean_distance.item())
# 유사도 측정 결과를 pyqt5 위젯에 표시...터미널X
#print(f"score : {getScore(euclidean_distance.item())}")
# 기존에 "유사도 안내 : 90%" 라고 출력하던 곳에 결과 출력
# self.[].setText() 함수 이용
# ex) self.text_label.setText('hello world') 형태...self는 함수에서 써야 함
# f"" 안에 띄어쓰기 -> 위젯에서 가운데에 표시하려고 함 (앞에 8칸 띄기)
self.check_word_4.setText(f" 유사도 안내 : {final_similar_score}%")
# ----------------------------------------------------------------
# ....나머지 코드 내용
# ----------------------------------------------------------------
# 유사도 점수 측정 함수
def getScore(dissimilarity):
if dissimilarity >= 2.0:
score = 0
else:
score = 100 - dissimilarity*50
score = round(score)
return score
# 유사도 측정 모델
class SiameseNetwork(nn.Module):
def __init__(self):
super(SiameseNetwork, self).__init__()
self.cnn1 = nn.Sequential(
nn.ReflectionPad2d(1),
nn.Conv2d(3, 4, kernel_size=3),
nn.ReLU(inplace=True),
nn.BatchNorm2d(4),
nn.ReflectionPad2d(1),
nn.Conv2d(4, 8, kernel_size=3),
nn.ReLU(inplace=True),
nn.BatchNorm2d(8),
nn.ReflectionPad2d(1),
nn.Conv2d(8, 8, kernel_size=3),
nn.ReLU(inplace=True),
nn.BatchNorm2d(8),
)
self.fc1 = nn.Sequential(
nn.Linear(8*99*250, 500),
nn.ReLU(inplace=True),
nn.Linear(500, 500),
nn.ReLU(inplace=True),
nn.Linear(500, 5))
def forward_once(self, x):
output = self.cnn1(x)
output = output.view(output.size()[0], -1) # flatten
output = self.fc1(output)
return output
def forward(self, input1, input2):
output1 = self.forward_once(input1)
output2 = self.forward_once(input2)
return output1, output2
# 나머진 위에 함수로 옮김
# ...
- 기존 코드를 뺴서 함수로 만든 이유
-
위젯에 표시하는 부분인 아래 코드가 self.~ 이고, 이는 함수 안에서만 사용 가능
→ 기존에는 “유사도 안내 : 90%”라고 정해진 글자를 보여주는 부분을 {final_similar_score}라는 값으로 측정할 때마다 변하는 결과값을 보여줄 수 있도록 바꿈
ex)
self.textEdit.setPlainText("Hello PySide6!")
위젯에 결과를 출력하는 부분
self.check_word_4.setText(f" 유사도 안내 : {final_similar_score}%")
전체 sub.py 코드 (주석처리는 라즈베리파이에서 작동하는 코드부분)
# ----------------------------------------------------------------
import os
import sys
#import spidev
from PyQt5 import uic
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import time
import math
import record
import torchvision
import torchvision.datasets as dset
import torchvision.transforms as transforms
from torch.utils.data import DataLoader,Dataset
import matplotlib.pyplot as plt
import torchvision.utils
import numpy as np
import random
from PIL import Image
import torch
from torch.autograd import Variable
import PIL.ImageOps
import torch.nn as nn
from torch import optim
import torch.nn.functional as F
import button_rc
# 추가...voice_code의 vad.py, mel.py
from voice_code import vad, mel
form_class = uic.loadUiType("./sub.ui")[0]
# ----------------------------------------------------------------
# ----------------------------------------------------------------
# button 기능 함수
class WindowClass(QMainWindow, form_class):
def __init__(self):
super().__init__()
self.setupUi(self)
self.checknoise.setHidden(True)
self.selectsexual.setHidden(True)
self.resultnoise.setHidden(True)
self.result.setHidden(True)
self.button_selectsexual.clicked.connect(self.uiselectsexual)
self.button_startchecknoise.clicked.connect(self.uichecknoise)
self.button_resultnoise.clicked.connect(self.uiresultnoise)
#self.button_resultnoise.clicked.connect(self.show_noise_result)
self.button_startrecord.clicked.connect(self.start_record)
self.button_stoprecord.clicked.connect(self.uiresult)
self.button_backtoselectsexual.clicked.connect(self.uiselectsexual)
self.button_backtochecknoise.clicked.connect(self.uichecknoise)
self.button_backtomain.clicked.connect(self.uimain)
self.button_rerecord.clicked.connect(self.uiresultnoise)
self.button_exit.clicked.connect(self.close)
noise = None
#self.spi = spidev.SpiDev()
#self.spi.open(0,0)
#self.spi.max_speed_hz = 1350000
self.dialog = QDialog()
self.noiselabel = QLabel(self.dialog)
self.buttongroup_sexual = QButtonGroup(self)
self.buttongroup_sexual.setExclusive(True)
self.buttongroup_sexual.addButton(self.check_man,1)
self.buttongroup_sexual.addButton(self.check_woman,2)
def uimain(self):
self.selectsexual.hide()
self.checknoise.hide()
self.resultnoise.hide()
self.result.hide()
self.mainwindow.show()
def uiselectsexual(self):
self.selectsexual.show()
self.checknoise.hide()
self.resultnoise.hide()
self.result.hide()
self.mainwindow.hide()
def uichecknoise(self):
self.selectsexual.hide()
self.checknoise.show()
self.resultnoise.hide()
self.result.hide()
self.mainwindow.hide()
self.noiselabel.clear()
# def read_sensor_data(self):
# # 사운드 센서값을 불러오는 함수
# while True:
# r = self.spi.xfer2([1, (8 + 0) << 4, 0])
# adc_out = ((r[1] & 3) << 8) + r[2]
# analog_value = adc_out
# if analog_value <= 0:
# analog_value = 1
# db_value = round(20 * math.log10(analog_value), 1)
# time.sleep(0.5)
# return db_value
def uiresultnoise(self):
self.selectsexual.hide()
self.checknoise.hide()
self.resultnoise.show()
self.button_stoprecord.hide()
self.button_startrecord.show()
#self.select_word.setPlainText(random(list))
self.result.hide()
self.mainwindow.hide()
# 녹음시작
def start_record(self):
# 녹음 시작 전에, 유사도 측정한 파일들 삭제
if os.path.exists("record_after_vad.wav"):
os.remove("record_after_vad.wav")
if os.path.exists("Mel_record_after_vad.jpg"):
os.remove("Mel_record_after_vad.jpg")
self.button_startrecord.hide()
self.button_stoprecord.show()
record.start()
# 녹음종료
def uiresult(self):
self.selectsexual.hide()
self.checknoise.hide()
self.resultnoise.hide()
record.stop()
self.result.show()
self.mainwindow.hide()
# 녹음 후 생긴 record.wav에 VAD, MEL 적용
new_record_file = "record_after_vad.wav"
vad.take_vad(new_record_file)
mel.take_mel(new_record_file)
# VAD, MEL 적용 전 원본 .wav랑 .jpg 삭제
os.remove("record.wav")
os.remove("Mel_record.jpg")
# 유사도 측정을 녹음 후에 실행하기
# 맨위에 __init__ 부분에 이어붙이면, 녹음 전에 먼저 실행됨...
self.similar_test()
# 유사도 측정 함수
def similar_test(self):
# 측정...
device = "cuda" if torch.cuda.is_available() else "cpu"
# 모델 이름 경로
model = torch.load("siamese_net_v4.pt", map_location=device)
# 비교하려는 이미지(.jpg)들의 경로
# x0 : 사용자가 녹음한 음성데이터의 Mel 이미지
# x1 : 기준이 되는 TTS 음성데이터의 Mel 이미지
x0 = Image.open("Mel_record_after_vad.jpg")
x1 = Image.open("data/testing/korean/Mel_spectrum_VAD_KsponSpeech_000091.jpg")
convert_tensor = transforms.Compose([transforms.Resize((99,250)),transforms.ToTensor()])
x0 = convert_tensor(x0).unsqueeze(0)
x1 = convert_tensor(x1).unsqueeze(0)
print(x0.shape)
print(x1.shape)
output1, output2 = model(x0, x1)
euclidean_distance = F.pairwise_distance(output1, output2)
final_similar_score = getScore(euclidean_distance.item())
# 유사도 측정 결과를 pyqt5 위젯에 표시...터미널X
#print(f"score : {getScore(euclidean_distance.item())}")
# 기존에 "유사도 안내 : 90%" 라고 출력하던 곳에 결과 출력
# self.[].setText() 함수 이용
# ex) self.text_label.setText('hello world') 형태...self는 함수에서 써야 함
# f"" 안에 띄어쓰기 -> 위젯에서 가운데에 표시하려고 함 (앞에 8칸 띄기)
self.check_word_4.setText(f" 유사도 안내 : {final_similar_score}%")
# def show_noise_result(self):
# self.dialog.setWindowTitle("Dialog")
# self.dialog.setWindowModality(Qt.ApplicationModal)
# self.dialog.resize(300, 200)
# noise = None
# db_value = self.read_sensor_data() # 사운드센서 값 불러옴
# noise = str(db_value) + "db"
# self.noiselabel.move(150, 100)
# self.noiselabel.setText(noise)
# if db_value > 40:
# self.noiselabel.setStyleSheet("COLOR : red")
# elif db_value <= 40 and db_value > 20:
# self.noiselabel.setStyleSheet("COLOR : yellow")
# else:
# self.noiselabel.setStyleSheet("COLOR : green")
# self.dialog.setWindowTitle("소음측정결과")
# self.dialog.exec()
#list = ['안녕하세요', '바가지','도깨비','고구마','누룽지','주전자']
# ----------------------------------------------------------------
# ----------------------------------------------------------------
class Config():
testing_dir = "./testing"
def imshow(img, text=None, should_save=False):
npimg = img.numpy()
plt.axis("off")
if text:
plt.text(75, 8, text, style='italic', fontweight='bold',
bbox={'facecolor': 'white', 'alpha': 0.8, 'pad': 10})
plt.imshow(np.transpose(npimg, (1, 2, 0)))
plt.show()
def show_plot(iteration, loss):
plt.plot(iteration, loss)
plt.show()
# ----------------------------------------------------------------
# ----------------------------------------------------------------
# 유사도 점수 측정 함수
def getScore(dissimilarity):
if dissimilarity >= 2.0:
score = 0
else:
score = 100 - dissimilarity*50
score = round(score)
return score
# 유사도 측정 모델
class SiameseNetwork(nn.Module):
def __init__(self):
super(SiameseNetwork, self).__init__()
self.cnn1 = nn.Sequential(
nn.ReflectionPad2d(1),
nn.Conv2d(3, 4, kernel_size=3),
nn.ReLU(inplace=True),
nn.BatchNorm2d(4),
nn.ReflectionPad2d(1),
nn.Conv2d(4, 8, kernel_size=3),
nn.ReLU(inplace=True),
nn.BatchNorm2d(8),
nn.ReflectionPad2d(1),
nn.Conv2d(8, 8, kernel_size=3),
nn.ReLU(inplace=True),
nn.BatchNorm2d(8),
)
self.fc1 = nn.Sequential(
nn.Linear(8*99*250, 500),
nn.ReLU(inplace=True),
nn.Linear(500, 500),
nn.ReLU(inplace=True),
nn.Linear(500, 5))
def forward_once(self, x):
output = self.cnn1(x)
output = output.view(output.size()[0], -1) # flatten
output = self.fc1(output)
return output
def forward(self, input1, input2):
output1 = self.forward_once(input1)
output2 = self.forward_once(input2)
return output1, output2
# 나머진 위에 함수로 옮김
# ...
# ----------------------------------------------------------------
# ----------------------------------------------------------------
# main문
if __name__ == '__main__':
app = QApplication(sys.argv)
ui = WindowClass()
ui.show()
exit(app.exec_())
# ----------------------------------------------------------------
실행 결과
9) 메인 프로그램에 기능 추가2 - 성별 선택 체크박스 인식 기능
기존 메인 프로그램에서 아직 기능 없이 위젯에만 있던 부분에 새로운 기능 추가
추가한 기능
- 성별 선택에 따라 생성되는 TTS 데이터가 달라짐
추가한 코드 부분
(중간에 기존 코드 그대로…)
- 실행 결과
-
성별 선택 체크박스에 따라 생성되는 TTS 음성 데이터의 성별이 달라지도록 구현 완료
10) 메인 프로그램에 기능 추가3 - 랜덤 단어 or 문장 선택 기능
추가한 기능
랜덤으로 단어를 선택하는 함수인 ‘select_random_word()’를 추가하여 단어나 문장이 저장되어 있는 파일(’words_list.txt’)을 불러오고, 이들 중 하나를 랜덤으로 출력
추가한 코드 부분
- 실행 결과
-
기존 텍스트 창에 랜덤으로 선택된 단어 기입
11) 메인 프로그램에 기능 추가3 - 랜덤 선택된 단어를 TTS가 인식
추가한 기능
랜덤으로 선택된 단어를 pyqt5 위젯 텍스트 창에 띄우고, 이 단어를 TTS가 인식하여 사용자의 발음 데이터와 비교하기 위한 기준 음성데이터를 출력하도록 수정
추가한 코드 부분
랜덤으로 선택된 단어를 전역변수로 설정
랜덤 단어 선택 함수 안에 전역변수를 저장하고, 이 변수에 선택된 단어 저장
TTS 코드파일 불러오는 부분에 전역변수 지정
man_tts.py와 woman_tts.py 코드의 run_tts() 함수에 해당 전역변수 추가
12) 메인 프로그램 코드의 오류 해결
[발생한 문제점]
유사도 측정 결과가 ‘74%’로 고정된 값만 위젯에 출력되는 문제
- 사용자가 녹음한 음성 데이터의 크기나 길이에 상관없이, 무조건 ‘74%’라는 값만 출력하여 정확도 측정이 불가한 상황 발생
- 사용자 녹음 데이터를 들어보니, 사용자의 목소리가 아니라 TTS 목소리가 녹음된 상태
원인
아래 ‘vad_mel_test()’ 함수에서 불러오는 vad.take_vad() 코드 함수의 문제
아래 vad.take_vad 함수 부분을 보면 ‘recent_file’ 호출 부분은 현재 디렉토리 경로 내에 존재하는 ‘.wav’파일들 중, 가장 최신의 .wav 파일을 갖고오는 기능을 하고 있음
- 프로그램 순서 상 “사용자 녹음 → TTS 음성데이터 생성” 순으로 작동이 되기 때문에, TTS 음성데이터가 가장 최신의 .wav로 인식되어 여기에만 vad를 적용함
- 실행을 한 번 밖에 안하기 때문에 이를 먼저 고쳐서 여러 .wav 파일을 전부 처리할 수 있도록 해야 함
해결
기존에 각각 다른 코드파일로 나뉘어져 있던 vad 알고리즘과 mel 알고리즘을 하나로 합친 후, for문을 통해 여러 번 전처리 실행하도록 수정함
수정한 wav_and_mel.py (해당 코드파일 이름을 preprocessing.py로 바꿀 예정)
(아래 코드 더 있음…위 코드는 일부분)
새로 작성한 코드의 함수를 호출하도록 수정
- 실행 결과
-
유사도가 고정된 값인 ‘74%’로 뜨던 문제 해결
13) 각종 이슈
-
ADC 소자 고장
새로 제작한 프레임의 테스트를 위하여 기존의 모든 연결을 제거 후, 테스트를 성공적으로 마치고 원래대로 되돌리기 위하여 소자를 연결하던 중에 연결 실수로 인하여 ADC소자에 과전압이 가해졌다. 측정 샘플코드를 실행시켜 보았을 때 결과가 제대로 안나오는 점과 연결 당시 타는 냄새가 났던 점으로 부터 소자가 완전이 고장난 것으로 결론을 지었다.
→ 현재(15일 수요일) 온라인에서 대체를 위한 새 소자를 주문하였으며 배송에는 4~6일 정도가 소요될 예정이다. 따라서 이번 주 데모영상에는 소음 측정기능을 제외하고 데모영상 시연을 진행 할 예정이다.
-
라즈베리파이 마이크 에러
데모 프로그램 실행 중 마이크의 녹음이 지금까지 제대로 진행되고 있지 않다는 것을 발견하였다. 원인을 찾아본 결과 파이가 마이크를 제대로 인식하지 못한다는 것을 발견하였다.
→ 마이크를 인식시키는 방법 and 기존 마이크의 대체가 가능한 방법을 생각중
-
구글 클라우드 실행 에러 (시간 동기화로 해결)
파이에서 데모 프로그램을 실행 시켰을 때, Google Cloud TTS api의 응답 부분에서 다음과 같은 에러가 발생하였다.
google.api_core.exceptions.Unauthenticated: 401 Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid a uthentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.파이 이외의 host에서는 문제없이 돌아가기 때문에, 사용자 인증관련 에러로 판단하였고 인증을 위해 여러 방법을 서치하였다. 실행한 방법들은 다음과 같다.
방법1) Google Cloud Console의 크레딧 전부 사용하여 만료되었는지 확인 → 실패
실패 이유 :
구글 클라우드 콘솔 서비스 TTS는 크레딧을 매우 조금 사용하고, 4월부터 계속 TTS를 테스트 해왔었으나 API 크레딧은 아직 거의 사용되지 않았다. = 크레딧 관련 문제X
방법2) 라즈베리파이5에 Google Cloud SDK(= CLI) 설치하여 인증 → 실패
문제가 발생한 파이의 운영체제인 Ubuntu를 선택하여 CLI를 설치하려 함
실패 이유 :
설치 관련 터미널 명령어를 입력해도, ‘해당 디렉토리가 존재하지 않는다’는 등의 에러 메시지가 지속적으로 발생했다.
novalid: [에러메시지~]방법3) Google Cloud Console의 OAuth 클라이언트 ID 받아서 인증 → 실패
방법을 서치하던 중, OAuth 클라이언트 ID 라는 기능을 발견했다. (현재 버전은 OAuth2)
Google Cloud Console의 OAuth 클라이언트 ID란, 액세스 토큰을 이용하여 해당 API를 사용하는 사용자에게 접근 권한을 주는 ID 인증 서비스이다.
아래와 같은 이름의 OAuth 인증 ID를 제작하고 터미널에서 TTS 코드 재실행…실패
실패 이유:
예전에 다른 host에서도 테스트를 진행했었을 때, 이 OAuth 클라이언트 ID를 받지 않았었는데도 잘 실행되었다. = OAuth 클라이언트 ID 인증과는 관련X
방법4) 라즈베리파이5의 시간 동기화 재설정 → 해결완료
→ 라즈베리파이의 시간동기화가 되어있지 않기 때문에 발생한 오류이며 시간동기화를 진행하여 이를 해결하였다.
→ 시간동기화가 문제였기 때문에 방법1과 방법2는 이 문제점과 관련X
14) HW 프레임 변경
중간점검 이후 HW 프레임 퀄리티가 떨어져보인다는 피드백을 받았다. HW 프레임 퀄리티를 올리기 위해서 여러 방법들을 모색하였다.
- 높낮이 조절 기능 추가 - X
-
실제로 파이를 연결하였을 때, 위에서 아래로 바라볼 경우 화면이 잘 보이지 않는 문제점이 발생했다. 이에 따라서 높낮이 조절 기능을 추가해 사용자가 원하는 높낮이를 조절하고 화명늘 직선으로 바라볼 수 있도록한다.
→ 3D프린팅 기술을 사용해서 제작하기에는 시간이 너무 오래걸릴 것으로 판단
→ 완제품을 사서 추가하기에는 오히려 조잡하고 퀄리티가 떨어져보일 것으로 판단
-
- 손잡이 추가 - X
-
손잡이를 추가해서 상자를 들기 편하게 할 수 있다
→ 이런 기능을 추가하는 것 보다 더 근본적인 원인을 찾아보고 분석하고자 함
-
더 근본적인 원인을 분석하기 위해 인터뷰를 진행해서 여러 의견을 수렴하였고 다음과 같이 분석하였다.
-
전체적으로 너무 어둡다
→ 터치스크린 자체도 검정색인데 HW 프레임 또한 검정색 시트지로 도배했기 때문에 외관적으로 칙칙한 느낌이 나서 거부감이 들 수 있다고 생각
-
화면이 눈에 잘 안들어온다
→ 사용자가 의자에앉아서 해당 화면을 바라볼 때 대각선으로 바라보게 되는데 화면은 일자로 고정되어있기 때문에 화면이 잘 안보일 수 있음
→ 큰 상자에 비해 터치스크린이 상대적으로 작아서 시선이 분산되어 화면이 눈에 들어오지 않을 수 있음
- 분석한 이유들을 바탕으로 HW 프레임을 변경하였다. 변경된 부분들은 다음과 같다
- 터치스크린과 대비되는 색상 밝은 시트지로 교체
- 사용자가 보기 편하도록 터치스크린을 위쪽으로 배치
- 사용자가 의자에서 앉아서 대각선으로 바라볼 수 있도록 기울기 조정
- 시선이 분산되지 않도록 터치스크린 화면을 앞으로 돌출
- 변경된 HW 프레임
15) 소프트웨어 진행상황
전체적인 코드
주석 처리된 부분의 경우 파이에서 사용하는 코드로써 파이 작동 시 부활예정
# ----------------------------------------------------------------
import os
import sys
# import spidev
import io
from PyQt5 import uic
from PyQt5.QtGui import QColor, QMovie, QFont, QPixmap
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import time
import math
import record
import torchvision.transforms as transforms
from PIL import Image
import torch
import torch.nn as nn
import torch.nn.functional as F
import random
import button_rc
# 추가...voice_code의 vad.py, mel.py
from voice_code import preprocessing, man_tts, woman_tts
form_class = uic.loadUiType("./sub.ui")[0]
# ----------------------------------------------------------------
# 추가) random word 전역변수 설정 부분
# =======================================================
global_selected_sentence = ""
# =======================================================
# ----------------------------------------------------------------
# button 기능 함수
class WindowClass(QMainWindow, form_class):
def __init__(self):
super().__init__()
self.setupUi(self)
self.checknoise.setHidden(True)
self.selectsexual.setHidden(True)
self.resultnoise.setHidden(True)
self.result.setHidden(True)
self.loading.setHidden(True)
self.button_selectsexual.clicked.connect(self.uiselectsexual)
self.button_startchecknoise.clicked.connect(self.uichecknoise)
self.button_resultnoise.clicked.connect(self.uiresultnoise)
self.button_resultnoise.clicked.connect(self.show_noise_result)
self.button_startrecord.clicked.connect(self.start_record)
self.button_stoprecord.clicked.connect(self.uiloading)
self.button_backtoselectsexual.clicked.connect(self.uiselectsexual)
self.button_backtochecknoise.clicked.connect(self.uichecknoise)
self.button_backtomain.clicked.connect(self.uimain)
self.button_rerecord.clicked.connect(self.uiresultnoise)
self.button_exit.clicked.connect(self.end_function)
self.checked_man =False
self.checked_woman = False
self.buttongroup_sexual = QButtonGroup(self)
self.buttongroup_sexual.addButton(self.check_man, 1)
self.buttongroup_sexual.addButton(self.check_woman, 2)
self.buttongroup_sexual.setExclusive(True)
self.check_man.clicked.connect(self.button_checked_man)
self.check_woman.clicked.connect(self.button_checked_woman)
# self.spi = spidev.SpiDev()
# self.spi.open(0,0)
# self.spi.max_speed_hz = 1350000
self.dialog = QDialog()
self.noiselabel = QLabel(self.dialog)
self.movie = QMovie('icons/loading.gif', QByteArray(), self)
self.movie.setCacheMode(QMovie.CacheAll)
# QLabel에 동적 이미지 삽입
self.loading_label.setMovie(self.movie)
self.movie.start()
self.timer = QTimer(self)
def button_checked_man(self):
self.checked_man = not self.checked_man
if self.checked_man:
self.check_man.setStyleSheet("border-image: url(./icons/checked_man.png);")
self.check_woman.setStyleSheet("border-image: url(./icons/unchecked_woman.png);")
if self.checked_woman:
self.checked_woman = not self.checked_woman
else:
self.check_man.setStyleSheet("border-image: url(./icons/unchecked_man.png);")
self.check_woman.setStyleSheet("border-image: url(./icons/unchecked_woman.png);")
def button_checked_woman(self):
self.checked_woman = not self.checked_woman
if self.checked_woman:
self.check_man.setStyleSheet("border-image: url(./icons/unchecked_man.png);")
self.check_woman.setStyleSheet("border-image: url(./icons/checked_woman.png);")
if self.checked_man:
self.checked_man = not self.checked_man
else:
self.check_man.setStyleSheet("border-image: url(./icons/unchecked_man.png);")
self.check_woman.setStyleSheet("border-image: url(./icons/unchecked_woman.png);")
def uimain(self):
self.selectsexual.hide()
self.checknoise.hide()
self.resultnoise.hide()
self.result.hide()
self.mainwindow.show()
def uiselectsexual(self):
self.selectsexual.show()
self.checknoise.hide()
self.resultnoise.hide()
self.result.hide()
self.mainwindow.hide()
def select_random_word(self):
# =======================================================
# 추가) 전역변수 설정 : 랜덤선택된 단어 -> TTS에서 사용
global global_selected_sentence
# =======================================================
# 파일에서 단어 리스트 읽어오기
# 리스트로 변환 (=한 줄씩 단어로 인식)
# 단어 리스트 중 랜덤하게 선택
f = io.open('words_list.txt', 'r', encoding='utf-8')
words_list = f.readlines()
random_word = random.choice(words_list).strip()
global_selected_sentence = random_word
return random_word
def uichecknoise(self):
self.selectsexual.hide()
self.checknoise.show()
self.resultnoise.hide()
self.result.hide()
self.mainwindow.hide()
self.noiselabel.clear()
self.selected_sentense = self.select_random_word()
self.given_sentense.setText(self.selected_sentense) # 단어리스트 랜덤하게 뽑아와서 넣으면 완료
self.given_sentense.setAlignment(Qt.AlignCenter)
# def read_sensor_data(self):
# 사운드 센서값을 불러오는 함수
# while True:
# r = self.spi.xfer2([1, (8 + 0) << 4, 0])
# adc_out = ((r[1] & 3) << 8) + r[2]
# analog_value = adc_out
# if analog_value <= 0:
# analog_value = 1
# db_value = round(20 * math.log10(analog_value), 1)
# time.sleep(0.5)
# return db_value
def uiresultnoise(self):
self.selectsexual.hide()
self.checknoise.hide()
self.resultnoise.show()
self.button_stoprecord.hide()
self.button_startrecord.show()
#self.select_word.setPlainText(random(list))
self.result.hide()
self.mainwindow.hide()
self.select_word.setText(self.selected_sentense) #제시된 단어 적기
self.select_word.setAlignment(Qt.AlignCenter)
db_value = 30 # self.read_sensor_data() # 사운드센서 값 불러옴
noise = str(db_value) + "db"
if db_value > 40:
self.present_db.setTextColor(QColor("Red"))
elif db_value <= 40 and db_value > 20:
self.present_db.setTextColor(QColor("Orange"))
else:
self.present_db.setTextColor(QColor("Green"))
self.present_db.setText(noise)
self.present_db.setAlignment(Qt.AlignCenter)
# 녹음시작
def start_record(self):
[os.remove(os.path.join('.', filename)) for filename in os.listdir('.') if filename.endswith('.wav')]
[os.remove(os.path.join('.', filename)) for filename in os.listdir('.') if filename.endswith('.jpg')]
self.button_startrecord.hide()
self.button_stoprecord.show()
record.start()
# 녹음종료
def uiresult(self):
self.selectsexual.hide()
self.checknoise.hide()
self.resultnoise.hide()
self.mainwindow.hide()
self.loading.hide()
self.result.show()
def uiloading(self):
record.stop()
self.loading.show()
self.loading.raise_()
# 유사도 측정을 녹음 후에 실행하기
# 맨위에 __init__ 부분에 이어붙이면, 녹음 전에 먼저 실행됨...
QTimer.singleShot(5000,self.vad_mel_test)
def vad_mel_test(self):
# TTS 생성 부분 추가
# =======================================================
if self.checked_man:
print("man 불러오기 완료")
man_tts.run_tts(global_selected_sentence)
else:
print("woman 불러오기 완료")
woman_tts.run_tts(global_selected_sentence)
# =======================================================
# 녹음 후 생긴 record.wav, tts에 VAD, MEL 적용
preprocessing.wav_to_mel()
self.similar_test()
# 유사도 측정 함수
def similar_test(self):
# 측정...
device = "cuda" if torch.cuda.is_available() else "cpu"
# 모델 이름 경로
model = torch.load("siamese_net_v4.pt", map_location=device)
# 비교하려는 이미지(.jpg)들의 경로
# x0 : 사용자가 녹음한 음성데이터의 Mel 이미지
# x1 : 기준이 되는 TTS 음성데이터의 Mel 이미지
x0 = Image.open("Mel_VAD_record.jpg")
x1 = Image.open("Mel_VAD_TTS_record.jpg")
convert_tensor = transforms.Compose([transforms.Resize((99,250)),transforms.ToTensor()])
x0 = convert_tensor(x0).unsqueeze(0)
x1 = convert_tensor(x1).unsqueeze(0)
output1, output2 = model(x0, x1)
euclidean_distance = F.pairwise_distance(output1, output2)
final_similar_score = getScore(euclidean_distance.item())
# 유사도 측정 결과를 pyqt5 위젯에 표시...터미널X
#print(f"score : {getScore(euclidean_distance.item())}")
# 기존에 "유사도 안내 : 90%" 라고 출력하던 곳에 결과 출력
# self.[].setText() 함수 이용
# ex) self.text_label.setText('hello world') 형태...self는 함수에서 써야 함
# f"" 안에 띄어쓰기 -> 위젯에서 가운데에 표시하려고 함 (앞에 8칸 띄기)
if final_similar_score < 33:
self.similar_score_text.setTextColor(QColor("Red"))
elif final_similar_score >=33 and final_similar_score < 66:
self.similar_score_text.setTextColor(QColor("Orange"))
else:
self.similar_score_text.setTextColor(QColor("Green"))
self.similar_score_text.setFont(QFont('Arial', 10, QFont.Bold))
self.similar_score_text.setFontPointSize(20)
self.similar_score_text.setText(f"유사도 안내 : {final_similar_score}%")
self.similar_score_text.setAlignment(Qt.AlignCenter)
self.similar_score_text.setStyleSheet("background-color: rgba(255, 255, 255, 0); border: 1px solid black; border-radius: 10px;")
self.uiresult()
def show_noise_result(self):
self.dialog.setWindowTitle("Dialog")
self.dialog.setWindowModality(Qt.ApplicationModal)
self.dialog.resize(300, 200)
# db_value = self.read_sensor_data() # 사운드센서 값 불러옴
db_value = 0
noise = str(db_value) + "db"
self.noiselabel.move(150, 100)
self.noiselabel.setText(noise)
if db_value > 40:
self.noiselabel.setStyleSheet("COLOR : red")
elif db_value <= 40 and db_value > 20:
self.noiselabel.setStyleSheet("COLOR : yellow")
else:
self.noiselabel.setStyleSheet("COLOR : green")
self.dialog.setWindowTitle("소음측정결과")
self.dialog.exec()
def end_function(self):
[os.remove(os.path.join('.', filename)) for filename in os.listdir('.') if filename.endswith('.wav')]
[os.remove(os.path.join('.', filename)) for filename in os.listdir('.') if filename.endswith('.jpg')]
self.close()
#list = ['안녕하세요', '바가지','도깨비','고구마','누룽지','주전자']
# ----------------------------------------------------------------
# ----------------------------------------------------------------
# 유사도 점수 측정 함수
def getScore(dissimilarity):
if dissimilarity >= 2.0:
score = 0
else:
score = 100 - dissimilarity*50
score = round(score)
return score
# 유사도 측정 모델
class SiameseNetwork(nn.Module):
def __init__(self):
super(SiameseNetwork, self).__init__()
self.cnn1 = nn.Sequential(
nn.ReflectionPad2d(1),
nn.Conv2d(3, 4, kernel_size=3),
nn.ReLU(inplace=True),
nn.BatchNorm2d(4),
nn.ReflectionPad2d(1),
nn.Conv2d(4, 8, kernel_size=3),
nn.ReLU(inplace=True),
nn.BatchNorm2d(8),
nn.ReflectionPad2d(1),
nn.Conv2d(8, 8, kernel_size=3),
nn.ReLU(inplace=True),
nn.BatchNorm2d(8),
)
self.fc1 = nn.Sequential(
nn.Linear(8*99*250, 500),
nn.ReLU(inplace=True),
nn.Linear(500, 500),
nn.ReLU(inplace=True),
nn.Linear(500, 5))
def forward_once(self, x):
output = self.cnn1(x)
output = output.view(output.size()[0], -1) # flatten
output = self.fc1(output)
return output
def forward(self, input1, input2):
output1 = self.forward_once(input1)
output2 = self.forward_once(input2)
return output1, output2
# 나머진 위에 함수로 옮김
# ...
# ----------------------------------------------------------------
# ----------------------------------------------------------------
# main문
if __name__ == '__main__':
app = QApplication(sys.argv)
ui = WindowClass()
ui.show()
exit(app.exec_())
# ----------------------------------------------------------------
유사도 검사 도중 로딩 추가
기획한 프로그램 제작 완료 및 ui 지속적으로 개선 중
Traceback (most recent call last):
File "c:\minus_github\Source_Code\sub.py", line 299, in end_function
[os.remove(os.path.join('.', filename)) for filename in os.listdir('.') if filename.endswith('.wav')]
File "c:\minus_github\Source_Code\sub.py", line 299, in <listcomp>
[os.remove(os.path.join('.', filename)) for filename in os.listdir('.') if filename.endswith('.wav')]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
PermissionError: [WinError 32] 다른 프로세스가 파일을 사용 중이기 때문에 프로세스가 액세스 할 수 없습니다: '.\\TTS_record.wav'
To Do
- 유사도 추론 모델 정확도 향상
- UI/UX 개선
- 메인 프로그램 기능 추가