주간 다이어리 - 8주차
활동 기록
팀 활동
- 4월 22일(월) 21:00 ~ 21:40 (진행상황 공유 미팅)
- 4월 23일(화) 10:00 ~ 11:50 (동작flow체크, 하드웨어 테스트 오프라인 미팅)
- 4월 23일(화) 13:00 ~ 15:00 (하드웨어 테스트)
- 4월 25일(목) 14:45 ~ 16:15 (하드웨어 테스트 및 프레임 재설계)
- 4월 26일(금) 21:00 ~ 22:00 (정기 미팅)
→ 총 7시간 진행
개별 활동
- 전준표
- 4월 22일(월) 14:00 ~ 16:00 Siamese networks 학습(1)
- 4월 23일(화) 21:30 ~ 22:30 데이터셋 수정
- 4월 23일(화) 22:40 ~ 23:30 Siamese networks 학습(2)
- 4월 24일(수) 16:00 ~ 18:00 Siamese networks 정확도 측정 알고리즘 구현
- 4월 25일(목) 16:20 ~ 17:20 Siamese networks 정확도 측정 알고리즘 구현
- 4월 27일(토) 14:45 ~ 19:00 정확도 결과 분석
→ 총 11시간 5분 진행
- 유재휘
- 2024.04.27 : 텍스트 입력 후 TTS 변환 & 저장 알고리즘 테스트 - 4시간
- 2024.04.28 : TTS ~ Mel 알고리즘 하나의 코드로 재테스트 - 1시간 30분
→ 총 5시간 30분 진행
- 조민수
- 2024.04.27 : 성별 선택 ui 추가 & 7인치 모니터에 맞는 크기로 ui 수정 작업 - 4시간 30분
- 2024.04.28 : UI 수정 작업 - 2시간
→ 총 6시간 30분 진행
- 이민석
-
2024.04.27: 사운드센서 아날로그값 → dB값으로 변환 코드 작성
→ 총 2시간 진행
-
진행 상황
1) Siamese networks 학습
- 1차 학습 내용
- training 데이터
- 한국인 발화 음성 90장
- 영어권, 아시아권 외국인 발화 음성 각 45장 총 90장
- testing 데이터
- 한국인 발화 음성 10장
- 영어권, 아시아권 외국인 발화 음성 각 5장 총 10장
- batch_size : 64, epoch : 100
- training 데이터
- 1차 학습 결과
- loss : 0.001 → 학습 자체는 잘 이루어진 것으로 보인다.
- 문제점
- 정확도를 측정할 평가 지표가 아직 없기에 학습이 정확하게 되었는지 판단 불가
- mel-spectrogram 데이터만 보았을 때 육안으로 한국인 음성인지 외국인 음성인지 파악이 불가능하기 때문에 학습의 결과를 파악 불가
- 2차 학습 내용
- 데이터 셋, batch size, epoch 등은 모두 동일
- 흑백이 아닌 원본 컬러 이미지 그대로를 사용하여 학습을 진행 → 컬러 이미지로 진행 과정에서 오류가 발생하여 수정하였다. 자세한 내용은 아래의 “에러” 항목 참고
- 2차 학습 결과
- loss : 0.0023
- 1, 2차 학습 결과 피드백
- 흑백, 컬러로 학습을 진행해본 결과 학습 과정 자체는 큰 차이가 없었다. 따라서 원본 데이터셋이 컬러인 점을 감안하여 추후의 학습은 모두 원본 그대로인 컬러 이미지를 이용하여 학습을 진행해 나갈 것이다.
- 정확도 측정 지표의 미비로 학습결과의 정확도를 측정하는 알고리즘의 개발이 필요
- 육안 혹은 다른 방법으로 유사도 측정 결과가 정확한지에 대해 파악하기 위한 방법의 고안이 필요해 보인다. ( 어느 mel-spectrogram이 한국인이고 외국인 데이터인지)
- 에러 처리
- Mel-spectrogram이미지를 컬러로 학습하는 과정에서 원본 이미지의 채널수가 계속 4채널로 인식되어 학습이 진행되지 않는 문제 발생
- → 테스트 결과, 기존 데이터셋이 RGBA형식의 4채널로 저장되어 있기 때문에 컬러맵을 갖고있는 상태에서 tensor로 변형시켰을 경우 4채널로 인식되었음
-
→ RGBA 이미지를 RGB로 변형시키고 jpg로 저장시키는 코드를 이용하여 모든 데이터셋을 수정
from PIL import Image import glob import os # 이미지 파일들의 경로 image_paths = glob.glob('CsponSpeech_Mel/*.png') output_dir = 'KsponSpeech_Mel/' # 이미지 읽어오기 images = [Image.open(img_path) for img_path in image_paths] cnt = 1 # 이미지를 순회하며 작업 수행 for img in images: img = img.convert("RGB") img.save(os.path.join(output_dir, f'c{cnt}.jpg')) cnt+=1
2) Siamese networks 정확도 측정 알고리즘 구현
테스트 학습을 진행 후 정확도 측정지표의 필요성을 느껴, 정확도 측정 알고리즘을 구현하였다.
def compute_accuracy(model, test_loader, threshold=0.5): #임계값 default : 0.5
correct = 0
total = 0
model.eval()
with torch.no_grad():
for data1, data2, labels in test_loader:
data1, data2, labels = data1.cuda(), data2.cuda(), labels.cuda()
output1, output2 = model(data1, data2)
euclidean_distance = F.pairwise_distance(output1, output2)
predicted = (euclidean_distance < threshold).float()
correct += (predicted == labels).sum().item()
total += labels.size(0)
accuracy = correct / total
print(f"correct : {correct}" )
print(f"total : {total}")
return accuracy
acc = compute_accuracy(net, test_dataloader)
print(acc)
- 문제점
- 정확도가 20%를 밑 도는 결과가 발생
- 원인 분석
- 해당 결과가 나온 원인을 분석하기 위하여 모델의 구조 및 데이터 셋을 전체적으로 분석해 본 결과, 정확도 향상을 위해서는 다음과 같은 두가지 요소의 조정이 필요할 것으로 예상된다.
- ContrastiveLoss 의 margin 값 조정
- 두 이미지간의 거리가 margin값보다 크면 손실 발생. 작으면 손실 0. 따라서 해당 값을 적절히 조절하는 것이 학습의 정확도 향상으로 이어질 수 있을 것임. 2. 정확도 측정 함수의 threshold 값 조정
- 현재의 default값으로는 정확한 측정이 불가능하였음. threshold 값을 수정하여 어느정 도의 유사도 부터 정확한 추론을 하였는지를 조정하면 이러한 문제는 해결될 것
- 해당 결과가 나온 원인을 분석하기 위하여 모델의 구조 및 데이터 셋을 전체적으로 분석해 본 결과, 정확도 향상을 위해서는 다음과 같은 두가지 요소의 조정이 필요할 것으로 예상된다.
3) 사운드 센서 테스트
사운드 센서의 정상 작동을 테스트 하던 중, 테스트 코드가 실행되지 않는 문제가 발생하였다.
- 에러 내용
- RuntimeError: Cannot determine SOC peripheral base address
- 원인 분석
- 센서 자체의 문제, 하드웨어의 문제의 가능성도 있었지만 해당 오류 내용을 검색해 본 결과 라즈베리파이의 호환성에 관한 문제의 가능성이 높았다.
- 기존에 사용하던 gpio 모듈이 라즈베리5에서 지원하지 않았기 때문에 발생하는 문제라는 것을 발견
- 라즈베리파이5 에서도 지원하는 모듈로 변경 후 해당 문제 해결
- 변경 전 : sudo apt install python3-rpi.gpio
- 변경 후 : pip3 install rpi-lgpio
- 결과
-
0과 1의 디지털 값으로 소음 출력 성공
→ 0~255의 범위의 아날로그 값으로 출력되도로 변경 필요
-
- 성공한 테스트 코드
import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BCM)
GPIO.setup(4, GPIO.IN)
while(True):
soundlevel = GPIO.input(4)
print("soundlevel", soundlevel)
time.sleep(0.2)
사운드 센서의 출력 값을 아날로그 값으로 변경
- PWM 방식으로 아날로그 출력
- 라즈베리파이5의 특정 GPIO 핀 번호에서 PWM 신호를 생성할 수 있음을 발견
- GPIO12(보드 핀 번호 32)
- GPIO13(보드 핀 번호 33)
- GPIO18(보드 핀 번호 12)
- GPIO19(보드 핀 번호 35)
- 사운드센서의 선 연결을 GPIO4 → GPIO18으로 바꾸고 테스트 코드 실행
- 결과
- 기존과 똑같이 0과 1의 디지털 값으로 소음 출력
- 원인 분석
- 코드 문제라고 판단되어 여러 테스트 코드들을 통해서 실행 해보았지만 여전히 똑같이 디지털 신호를 출력
- 코드 문제가아니라 라즈베리파이 기기 자체의 문제라는 것을 발견
- 라즈베리파이 기기 자체가 아날로그 출력값을 지원하지 않아서 MCP3008을 이용하여 디지털 값을 아날로그 값으로 출력할 수 있도록함
- MCP3008 장착 후 해당 문제 해결
- 결과
-
특정 범위 안에서의 아날로그 출력 성공
→ 0~255 범위로 출력되도록 변경 필요, 변경된 아날로그 값을 db변환 필요
-
- 성공한 테스트 코드
import time
import spidev
spi = spidev.SpiDev()
spi.open(0,0)
spi.max_speed_hz = 1350000
def analog_read(channel):
r = spi.xfer2([1, (8+channel) << 4, 0])
adc_out = ((r[1]&3) << 8) + r[2]
return adc_out
while True:
reading = analog_read(0)
print(reading)
time.sleep(0.2)
4) 하드웨어 테스트 및 프레임 재설계
라즈베리파이5에 3.55mm잭이 없던 이슈로 못했던 스피커 테스트를 진행하였다.
- 작동은 잘 되지만 소리를 너무 키울경우 가만히 있어도 노이즈 발생
- 소리를 적당히 조절하여 미세한 노이즈가 있는 상태로 개발 진행
기존의 HW 프레임을 2층 구조에서 1층 구조로 변경하였다
- 기존의 2층 구조 HW는 크기만 크고 실용성이 떨어짐
- 기존의 HW 구조에서 마이크 거치대가 너무 큰 크기를 차지하고 있다고 판단
- 마이크 거치대 제거 → 사용자가 직접 마이크를 들고 말하는 방식으로 변경
- 기존의 2층 구조는 HW 제작 시간이 오래걸리는것에 비해 결과물이 만족스럽지 않을 것으로 판단
- 2층 → 1층 구조로 바꾸면서 HW 프레임을 간소화하고 HW 제작에 있어서 걸리는 시간을 단축하기로 변경
5) TTS ~ Mel 알고리즘 구현
tts.py
# ----------------------------------------------------------------
import os
import datetime
from gtts import gTTS
# ----------------------------------------------------------------
# ----------------------------------------------------------------
# TTS 기능 함수
def text_to_speech(text, lang='ko'):
tts = gTTS(text=text, lang=lang)
current_time = datetime.datetime.now().strftime('%Y%m%d_%H-%M-%S')
filename = f"tts_{current_time}.wav"
delete_old_file()
tts.save(filename)
return filename
# ----------------------------------------------------------------
# ----------------------------------------------------------------
# .wav 파일 자동 삭제 함수
# 디렉토리 내부의 .wav 파일이 10개를 넘어가는 경우에 가장 오래된 파일부터 하나씩 지워줌
def delete_old_file():
current_directory = os.getcwd()
wav_files = [file for file in os.listdir(current_directory) if file.endswith(".wav")]
if len(wav_files) >= 11:
oldest_file = min(wav_files, key=os.path.getctime)
os.remove(os.path.join(current_directory, oldest_file))
print(f"기존 '{oldest_file}' 파일 삭제")
# ----------------------------------------------------------------
# ----------------------------------------------------------------
def main():
text = input("텍스트 입력: ")
filename = text_to_speech(text)
print(f"신규 .wav파일 '{filename}'로 저장 완료")
if __name__ == "__main__":
main()
# ----------------------------------------------------------------
mel_wav.py
# ----------------------------------------------------------------
import os
import librosa
import librosa.display
import numpy as np
import matplotlib.pyplot as plt
# ----------------------------------------------------------------
# ----------------------------------------------------------------
# 디렉토리 내의 .wav 파일 목록 가져오기
def get_wav_files(directory):
wav_files = []
for file in os.listdir(directory):
if file.endswith(".wav"):
wav_files.append(os.path.join(directory, file))
return wav_files
# ----------------------------------------------------------------
# ----------------------------------------------------------------
# 현재 실행 중인 파일의 경로
current_directory = os.path.dirname(os.path.abspath(__file__))
# 경로 내 .wav 파일 목록 가져오기
wav_files = get_wav_files('.')
# ----------------------------------------------------------------
# ----------------------------------------------------------------
# 디렉토리 내 .png 파일 삭제 함수
def delete_old_png_file():
current_directory = os.getcwd()
png_files = [file for file in os.listdir(current_directory) if file.endswith(".png")]
if len(png_files) >= 11:
oldest_file = min(png_files, key=os.path.getctime)
os.remove(os.path.join(current_directory, oldest_file))
print(f"기존 '{oldest_file}' 파일 삭제")
# ----------------------------------------------------------------
# ----------------------------------------------------------------
# 각 .wav 파일을 불러와서 처리
for audio_file in wav_files:
# 오디오 데이터 읽어오기
y, sr = librosa.load(audio_file)
# Mel-spectrogram 계산
mel_spectrogram = librosa.feature.melspectrogram(y=y, sr=sr)
# Mel-spectrogram을 데시벨로 변환
mel_spectrogram_db = librosa.power_to_db(mel_spectrogram, ref=np.max)
# Mel-spectrogram 플로팅
plt.figure(figsize=(10, 4))
librosa.display.specshow(mel_spectrogram_db, sr=sr, x_axis='time', y_axis='mel')
# plot 그래프 이미지만 나타내기]
plt.yticks(ticks=[]) # y축 tick 제거
plt.xticks(ticks=[]) # x축 tick 제거
# # x축, y축 각각 눈금 & 테두리 제거
plt.gca().axes.get_xaxis().set_visible(False)
plt.gca().axes.get_yaxis().set_visible(False)
plt.gca().spines['bottom'].set_visible(False)
plt.gca().spines['left'].set_visible(False)
plt.gca().spines['top'].set_visible(False)
plt.gca().spines['right'].set_visible(False)
# 제목과 컬러바(색상표시바) 제거
plt.title('')
plt.colorbar().remove()
# 그래프 영역에 맞게 이미지 크기 조정
plt.tight_layout()
# 그래프 영역에 맞게 이미지 크기 조정
plt.subplots_adjust(left=0, right=1, top=1, bottom=0)
# 축 비활성화하여 그래프 내용만 자르기
plt.axis('off')
# 이미지 저장
output_filename = f"Mel_{os.path.splitext(os.path.basename(audio_file))[0]}.png"
plt.savefig(output_filename, bbox_inches='tight', pad_inches=0)
# 플롯 닫기
plt.close()
# 이미지 파일 삭제 함수 작동
delete_old_png_file()
# ----------------------------------------------------------------
main.py
import os
import subprocess
# tts.py 실행
print("TTS 작동 중")
subprocess.run(["python", "tts.py"])
# mel_wav.py 실행
print("Mel spectrum 작동 중")
subprocess.run(["python", "mel_wav.py"])
print("실행 종료")
입력한 텍스트를 gtts를 통해 음성 파일(.wav)로 변환 후, 이 변환된 음성 파일의 Mel spectrogram을 분석하여 plot 이미지(.png)로 저장하는 기능
- main.py 코드를 실행하면 순서대로 tts.py → mel_wav.py 코드를 실행
- 같은 디렉토리 안에 .wav파일과 .png파일 저장
-
만약 해당 파일들이 각각 10개를 넘어가면, 저장을 할 때마다 제일 오래된 파일부터 하나씩 제거하고 저장
-
6) 화면 ui제작 및 수정 작업
기존 ui 화면 진행 작업
- 학습시작 → 단어 선택 → 소음 측정 → 녹음 → 결과 →재실행 or 종료 선택
회의 결과로 수정 된 진행 화면 작업
- 학습 시작 → 성별 선택 → 소음 측정(단어 제시) → 소음 측정 결과 → 녹음 → 결과 실행 → 재실행 or 종료 선택
기존 진행한 ui 화면 중 수정할 단어선택 화면 및 소음 측정 화면 수정 작업 진행
추가되어야 할 성별 선택 화면 및 소음측정 결과 화면 추가 작업 진행
- 성별 선택 화면
-
소음 측정 화면 수정 화면
기존 작업하였던 뒤로 버튼을 추가하여 화면간 전환을 자유롭게 선택할 수 있도록 수정 작업 추가
수정 된 코드 부분
class WindowClass(QMainWindow, form_class):
def __init__(self):
super().__init__()
self.setupUi(self)
def nextpage(self):
self.hide()
self.next = SelectSexwindow()
self.next.exec()
self.show()
class SelectSexwindow(QDialog, QWidget, form_selectsexwindow):
def __init__(self):
super(SelectSexwindow, self).__init__()
self.initUI()
self.show()
def initUI(self):
self.setupUi(self)
def nextcheckNoise(self):
self.hide()
self.second = check_noisewindow()
self.second.exec()
self.show()
def backtomain(self):
self.close()
class check_noisewindow(QDialog,QWidget,form_checknoisewindow):
def __init__(self):
super(check_noisewindow,self).__init__()
self.initUi()
self.show()
def initUi(self):
self.setupUi(self)
def nextNoise(self):
self.hide()
self.nextnoise = noisewindow()
self.nextnoise.exec()
self.show()
def backtoselectsex(self):
self.hide()
self.nextnoise = SelectSexwindow()
self.nextnoise.exec()
self.show()
단어를 제시하는 화면의 경우 QTextBrowser를 사용하여 리스트에 불러오는 단어 중 랜덤하게 하나를 선택하여 보여주는 식으로 설정하였다.
- ui 전체적인 수정 부분 : 기존 작업한 프로그램을 모니터에 실행하였을 때 옆 부분 공백이 심한 것을 확인 → 옆부분 공백을 메꾸기 위해서 브라우저의 화면 Size를 수정 작업 및 전체적인 비율에 맞추어 크기 수정
7) 사운드센서 아날로그값 → dB(데시벨)값으로 변경
- 기존의 MCP3008을 사용하여 라즈베리파이에서 아날로그 값을 출력하는데에 성공하였다
- MCP3008은 10bit이기 때문에 0~1023사이의 값을 출력하며 이를 dB로 변환하고자 하였다
- 아날로그 값을 dB로 변환하는 공식은 다음과 같다
- 20 x log10(아날로그 값 / 참조 전압)
- MCP3008의 참조전압은 5V
위에 설명을 참고하여 다음과 같은 코드를 작성하였다
import time
import spidev
import math
spi = spidev.SpiDev()
spi.open(0,0)
spi.max_speed_hz = 1350000
def analog_read(channel):
r = spi.xfer2([1, (8+channel) << 4, 0])
adc_out = ((r[1]&3) << 8) + r[2]
return adc_out
#아날로그 값을 데시벨로 변환하는 함수
def analog_to_db(analog_value):
db_value = 20 * math.log10(analog_value / 5)
return db_value
while True:
reading = analog_read(0)
db_value = analog_to_db(reading)
print("데시벨 값:", db_value)
time.sleep(0.2)