이번에는 MFC를 활용해서 학교에서 배우는 신호처리 내용들을 직접 실습해보고, 원리를 이해해볼 수 있는 프로그램을 구현해보도록 하겠다.
이글은 MFC에 대한 지식이 어느정도 있고, 통신이론의 개념을 알고있는 사람들이 이해할 수 있을 것 같다.
물론 매트랩이나 mathematica와같은 교육용 툴이 잘 되어있지만 MFC를 이용해서도 충분히 좋은 프로그램을 만들 수 있다는걸 소개하고 싶다.
위의 프로그램은 진행한 프로젝트의 결과이며, 필터로 sin1Hz~sin30Hz까지 더한 함수를 두었고 이 함수를
sin 1Hz + sin 100Hz 와 convolution하여 LPF의 결과를 나타낸 모습이다.
해당 프로그램의 메뉴구성은 아래와 같다.
먼저 signal1,2로 여러가지 신호들을 만들 수 있다. 밑의 Chbyshev와 Butterworth또한 필터이고, 그려진다. 다만 주파수축에서 그려지는 느낌으로 그려지기에 옮겨주는 과정이 필요하긴 하다. 이에대한 내용은 아래에서 조금 더 자세히 다루어보도록 하겠다.
해당 프로그램은 위와같은 신호연산들을 제공한다.
위 프로그램은 display설정이
1 4
2 5
3 6
위와같은 배치로 되어있다. 연산은 1번과 2번자리의 연산을 해서 3번으로 들어가게 되어있다.
그래서 필요한게 위의 신호 이동 메뉴버튼이다. 모든 연산이 1번과 2번을 해서 3번에 저장되는 형식이기에 위처럼 옮기는게 있어야 편리하다.
이리저리 신호들의 위치를 바꾸는것이 가능하며, Fourier변환을 통해 바꾸고 곱을 하고싶은 경우에도
시간축 <> 주파수 버튼을 통해서 바꾸어 줄 수 있다.
예를들어 같은 예제를 위로 하면 이렇게 시간,공간이 바뀌게 된다.
화면 클리어 부분에서는 리셋과 512이후 자르기가 되어있는데, 나는 신호들을 1024개 혹은 512개를 보기를 원했는데, 512개를 가지고 실습을 하다보면 컨볼루션 등의 결과로 1024개가 반복되어 나오는 걸 알 수 있다. 이는 디지털 신호의 특징이니 넘어가도 괜찮겠다.
무튼, 이 튀어나온 부분이 상당히 거슬리고, 다른계산을해도 남아있어 보는데 불편한데 그 부분을 없애준는 메뉴라고 생각하면 될 것 같다.
위와같은 상황에서 주파수 확대버튼을 누르게 되면
이렇게 해당 주파수부분을 확대해서 볼 수 있다. 특히, 낮은 주파수들이 뭉쳐져 있는 부분을 볼때 편리하다.
위는 프로젝트를 구현하기위해 만들었던 헤더파일이다. 물론 mfc생성시 자동으로 생성되는 클래스들도 있다. 위 헤더파일의 자세한 내용을 알고 싶으면 블로그 메뉴의 C++항목에서 찾을 수 있다.
// DSPview.h
public:
double m_display[6][No_Total];
double m_SaveData[No_Total];
int m_depth;
CString Text[6];
double m_maxnum[6];
위가 view.cpp 파일에서 사용할 변수들이다.
프로그램에 표시될 6개의 디스플레이 공간을 No_Total(1024로 define되어있음) 개를 정의하고,
자리 옮기기 위해 임시로 왔다갔다하며 넣을 데이터와
데이터 확대 축소를 위한 m_depth
display 신호위에 comment를 간단히 달아줄 Text변수
m_maxnum은 우선은 무시해도 좋다.
>> m_maxnum은 현재 프로그램은 신호의 값이 어떻게 들어가도 그 안의 최대,최소값을 계산해주어 상대적으로 신호가 다른 공간을 침범하지 않게 잡아주는 기능이 설정되어 있는데, 그기능을 해제하고자 할때 보려했던 변수로 6개까진 굳이 필요없다.
CDSPView::CDSPView() noexcept
{
// TODO: 여기에 생성 코드를 추가합니다.
for (int k = 0; k < 6; k++) {
for (int n = 0; n < No_Total; n++) {
m_display[k][n] = 0;
}
m_maxnum[k] = 1;
}
m_depth = 1;
}
우선 생성코드를 하는 부분에서 위처럼 값들을 모두 초기화를 시켜준다.
void CDSPView::OnDraw(CDC* pDC)
{
CDSPDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
Draw_XY CD;
CD.Disp_Coor(pDC);
Disp_signal DS;
CPoint CP;
int depth = 1;
CString text;
pDC->SetTextColor(RGB(221, 221, 221));
CP.x = 20;
CP.y = 220;
for (int i = 0; i < 6; i++) {
if (i == 3) CP.x = 1070, CP.y = 220 , depth = m_depth;
for (int n = 0; n < No_Total; n++)
{
DS.Disp_Data[n] = m_display[i][n];
}
DS.Display_signal_CP(pDC, CP,depth);
text.Format(_T(" % f"), DS.maxnum);
pDC->TextOut(CP.x+10,CP.y-120, Text[i]);
pDC->TextOut(CP.x + 200, CP.y - 120, text);
CP.y += 200;
}
// TODO: 여기에 원시 데이터에 대한 그리기 코드를 추가합니다.
}
그 이후, 그리기 파트에서 그림을 그려준다. 이벤트 처리 함수를 통해서 display 변수에 값이 바뀐 후에, 그 값을
Invalidate()함수를 통해 위 onDraw함수를 호출하게 되어 그림이 그려지는 것이다.
즉, 위처럼 sin1Hz버튼을 클릭하였을 경우 display[0] 의 공간에 sin1Hz의 수식적인 값들이 들어가게 되고, 그 후 onDraw함수가 호출되어 그려지는 것이다.
어차피 display[6]이란 배열로 선언을 해주었기 때문에 for문을 사용해서 x,y좌표값들만 변경해가면서 화면에 출력되게 나타내었다.
신호들을 생성하는 부분은 모두 GenSignal이란 클래스를 거쳐서 작동하게 된다. 우선 메뉴에 이벤트 처리기를 연결하여 함수를 만든 후에,
void CDSPView::OnSignal1Sin1hzsumSin100hz()
{
Gen_Signal GS;
GS.m_string = "sin";
GS.DATA_No = No_Total / 2;
GS.m_FreQ = 1;
GS.Signal_Data();
for (int n = 0; n < No_Total; n++)
{
m_display[0][n] = GS.result_Data[n];
}
GS.m_FreQ = 10;
GS.Signal_Data();
for (int n = 0; n < No_Total; n++)
{
m_display[0][n] += GS.result_Data[n];
}
Text[0] = "sin1hz + sin 10hz";
Invalidate();
}
위처럼 GenSignal클래스를 활용하였다.
#include "pch.h"
#include "GenSignal.h"
#define Pi 3.141592
Gen_Signal::Gen_Signal(void)
{
for (int n = 0; n < No_Total; n++)
{
result_Data[n] = 0;
}
}
Gen_Signal::~Gen_Signal(void)
{
}
void Gen_Signal::Signal_Data()
{
int x;
if ((m_string) == "sin")
{
for (x = 0; x < DATA_No; x++)
{
result_Data[x] = sin((2 * Pi * m_FreQ / DATA_No) * x);
}
}
else if ((m_string) == "cos")
{
for (x = 0; x < DATA_No; x++)
{
Cos_Data[x] = cos((2 * Pi * m_FreQ / DATA_No) * x);
}
}
else if ((m_string) == "sinc")
{
Sinc_Data[0] = 1;
for (x = 1; x < DATA_No; x++)
{
result_Data[x] = (sin((2 * Pi * m_FreQ / DATA_No) * x))/ ((2 * Pi * m_FreQ / DATA_No)*x);
}
}
else if ((m_string) == "sa")
{
Sa_Data[0] = 1;
for (x = 1; x < DATA_No; x++)
{
result_Data[x] = 10 * sin(1.0 * x) / (1.0 * x );
}
}
else if ((m_string) == "sum")
{
for (x = 1; x < DATA_No; x++)
{
result_Data[x] = (cos((2 * Pi * m_FreQ / DATA_No) * x) + sin((2 * Pi * 10 / DATA_No) * x) + sin((2 * Pi * 5 / DATA_No) * x))/3;
}
}
else if ((m_string) == "white")
{
for (x = 1; x < DATA_No; x++)
{
result_Data[x] = (double((rand() % 100) -50)/ 100);
}
}
else if ((m_string) == "chev")
{
double square_epsilon = 1 / delta - 1;
double Cn;
for (x = 1; x < DATA_No; x++)
{
if (x > cut_fre)
Cn = cosh(n * acosh(x / cut_fre));
else
Cn = cos(n * acos(x / cut_fre));
result_Data[x] = 1 / (sqrt(1 + square_epsilon * Cn * Cn));
}
}
else if ((m_string) == "butter") {
for (x = 1; x < DATA_No; x++)
{
result_Data[x] = 1 / (sqrt(1 + pow((x / cut_fre), 2 * n)));
}
}
}
gensignal함수는 위와 같이 구성되어 있으며 들어오는 문자열 값에 따라서 해당 함수들을 반환해주게 된다.
그이후에 신호연산을 하는과정들도 과정이 유사한데,
void CDSPView::OnFourierTransform()
{
FourierTransform FT;
for (int x = 0; x < 3; x++)
{
for (int n = 0; n < No_Total; n++)
{
FT.m_InFTData[n] = m_display[x][n];
}
FT.Foruiertransform();
for (int n = 0; n < No_Total; n++)
{
m_display[x+3][n] = FT.m_OutFTData[n];
}
Text[x + 3] = Text[x];
}
Invalidate();
}
FourierTransform이나 다른 연산들을 수식에 의해 정의를 해두었다.
//class of FourierTransform
void FourierTransform::Foruiertransform()
{
double m_Real, m_Image;
double T_Real, T_Image;
for (int fre = 0; fre < 1024; fre++) {
T_Real = T_Image = 0;
for (int n = 0; n < 1024; n++) {
m_Real = m_InFTData[n] * cos(2 * Pi * fre * n / 512);
m_Image = m_InFTData[n] * sin(2 * Pi * fre * n / 512);
T_Real += m_Real;
T_Image += m_Image;
}
m_OutFTData[fre] = sqrt(T_Real * T_Real + T_Image * T_Image);
}
}
이 부분이 class에서 구현해둔 부분이다.
위 식을 그대로 코드로 옮겨본 것인데, 원래 엄밀히 말하면 푸리에변환은 크기와 위상 값들을 모두 고려하면서 해야하지만, 우선은 필터들의 설계등의 간단한 부분을 확인하고자 하기 때문에 위상값은 고려하지 않고, 크기 즉 magnitude 값만 고려하여 작성하였다.
convolution,correlation 모두 같은 동작원리로 작동하며, 해당 식의 수식만 따왔다.
//convolution
void Convolution::convolution()
{
for (int n = 0; n < No_Total; n++)
{
for (int k = 0; k <= n; k++)
{
Yn[n] += Xn[k] * Hn[n - k];
}
}
}
위는 convolution을 코드로 옮겨본 모습이다.
이렇게 메뉴와 버튼들을 연동해두면 결국 X(n)*H(n) = Y(n)을 구현해낼수 있는 프로그램을 만들 수 있게 된 것이다.
원래는 여기까지였으나 주파수끼리 곱하거나, 푸리에 역변환을 하는 등의 기능을 추가하고 싶어서 하나 둘씩 기능을 추가하게 되다 보니 기능이 좀 많아지게 되었다.
처음 프로그램을 만들때는 통신과목에 대한 이해 등등을 위주라서 별 기능이 없었는데, 코딩을 하면서 기능을 직접 추가하고 또 이 수식들을 프로그래밍으로 옮기는 과정들을 하면서 이 연산들에 대한 이해도도 더 커진것 같다.
++ 원래 바탕도 하얀색인데 검은색으로 바꾸는 부분도 추가했다. 만약 다시 하얀색으로 바꾸고싶다면, Disp와 DrawXY 클래스에 들어가서 수식값들을 직접 바꿔주면 되겠다.
+++ 숫자는 최대값을 의미한다. 그래프를 편의상 크기를 상대적으로 놔두었는데, whitenoise같은 경우 거의다 0에 근접한 값이기때문에 엄청 noise가 큰것처럼 느껴지게 된다. 이부분을 헷갈리지 않기위해서 추가한 기능이다.
'프로젝트 > 소규모프로젝트들' 카테고리의 다른 글
mdx Editor만들기 (마크다운 에디터) (1) | 2022.11.27 |
---|---|
JS_리액트_TodoList (0) | 2021.12.27 |
JS_독서관리 시스템 (2) | 2021.12.10 |
GitHub 클론코딩-2 (0) | 2021.11.27 |
GitHub 클론코딩-1 (0) | 2021.11.26 |