DSP 실험 2번째 주에는 행렬연산에 대하여 배웠습니다. 수업시간에는 그냥 포인터에 배열을 할당하여, 행렬을 만들고, transpose한 행렬을 하나 더 만들어 두 행렬을 곱하는 연산을 해보았는데요. 사실 처음에 프로그래밍 언어에 익숙해지고, 내용에 익숙해지기 위해서는 직접 만들어서 실행해 보는 것이 좋지만, 익숙해지고 나서도 직접 만들어 쓰는 것은 피곤한 일이기도 하고, (물론 완벽하게 코드를 쓰시는 분에게는 다른 이야기겠지만)프로그램의 에러 확률이 높아지는 원인이 되기도 하죠. (옛날에 자료구조를 배우고 난 직후 스택이랑 큐, 벡터 등 자료구조들을 일일히 직접 만들어 쓰던때가 생각나네요 ㅋㅋ)
서론이 길었네요 ㅎㅎ. 어쨋든 이 포스팅에는 이러한 매트릭스 연산 뿐만 아니라 선형연산에 대하여 많은 함수 및 클래스를 지원하는 라이브러리인 Eigen library에 대하여 소개를 하고자 합니다.
먼저 Eigen library를 쓰려면 Eigen library를 다운받아야 겠죠? 구글에 Eigen이라고 검색을 하거나 아래의 링크를 클릭하여 Eigen library를 받아줍니다.
캬 Eigen is a C++ template library for linear algebra: matrices, vectors, numerical solvers, and related algorithms.
말 그래도 입니다. 그냥 선형대수학에 관한 것들은 거의 다 있죵 ㅎㅎ. 흠.. latest stable release가 현재로써는 3.2.5버젼이네요 ㅋㅋ Get it에서 library를 다운로드 해줍니다. 압축포맷이 여러가지가 있는데, 다 똑같은 겁니다. 따로 압축 프로그램이 안깔려 있다면 그냥 zip파일을 다운로드 받아서 원하는 곳에 압축을 풀어줍시다!(용량이 얼마 안돼서 금방 다운로드 될겁니다!)
압축푼 폴더를 보면
캬 이것저것 많이 있네요 ㅎㅎ cmake 어쩌고 저쩌고 써져있는 것들은 cmake로 라이브러리를 따로 만들때 사용되는 것들이랍니다. 뭐 지금 단계에선 알필요 없어요. 아, 그리고 eigen library는 따로 설치 같은게 필요없습니다. 그냥 간단하게 파일 경로만 인클루드 시켜주면 되요!
어쨋든... 다운로드 받아서 압축을 풀어주셨다면 이제 Eigen library를 사용할 준비가 다 된 것입니다. ㅋㅋ 이제 Eigen을 이용한 프로그램을 만들기 위해 Visual studio를 실행하도록 하죠!
즐거운 마음으로 콘솔 어플리케이션 프로젝트를 만들어 줍니다.
프로젝트를 클릭한 후 alt+enter 또는 우클릭 properties 또는 메뉴바 Project-properties클릭을 하여 속성창을 열어줍니다!
VC++ Directories에서 Include Directories 편집창을 열어줍니다. 직접 path쓰셔도 됩니다.
저같은 경우는 Downloads에 압축을 풀었습니다. eigen library 압축푼 폴더로 경로를 설정해줍니다. Select Forder누르고 ok ok!
그럼 이제 cpp파일에 Eigen library를 이용한 간단한 프로그램을 만들어 봅시다.
#include <iostream>
#include <Eigen/Dense>
#define PI 3.14159265359
using namespace Eigen;
using namespace std;
int main()
{
MatrixXd m1 = MatrixXd(8, 8);
MatrixXd m2, m3;
//8*8행렬 m1에 값을 넣어줍니다.
double* alpha = new double[8];
for (int i = 0; i < 8; i++) {
if (i == 0)
alpha[i] = 1. / (2.*sqrt(2));
else {
alpha[i] = 0.5;
}
}
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
m1(i,j) = alpha[i] * cos((PI*(2.*j + 1)*i) / 16);
}
}
delete alpha;
m2 = m1.transpose();//m2에 m1을 transpose한 값을 넣어줍니다.
m3 = m1*m2;//m3에 m1과 m2를 곱한 값을 넣어줍니다.
cout <<"m1"<<endl<< m1 << endl <<"m2"<<endl<< m2 << endl <<"m3"<<endl<< m3 << endl;//출력!
return 0;
}
코드를 실행해보면!
m1과 m2와 m3가 출력된 것을 볼 수 있습니다. m3는 identity matrix이군요!(아주 작은 값들은 0이라고 봅시다 ㅎㅎ 1,2,3의 10의 -13승이니 0이라고 봐도 무방하죠.) 그렇다면 m1행렬을 A라 했을때,
행렬 A는 orthogonal matrix(직교행렬)라고 볼 수 있겠군요!
Eigen library에 대한 자세한 documentation은 Eigen 페이지에 가면 있습니다.
DSP 실험 첫번째 주에는 sin 곡선 그리기를 배웠는데요. 실험 내용은 간단했습니다. 그냥 c / c++에서 sin함수를 써서 데이터를 파일 출력으로 데이터를 저장한 다음 excel 외부데이터 가져오기를 통해 그래프를 그리고, 이를 통해 앨리어싱을 확인하는 실험을 했습니다.
하지만 이렇게 그냥 넘어가기는 아쉬우니 Microsoft Chart Controls과 C#을 이용하여 직접 앨리어싱을 확인하는 프로그램을 한번 짜보도록 하죠.
(Microsoft Chart controls는 VB와 C#으로 이용할 수 있습니다.)
일단 windows Forms Application에서 ms chart를 사용하려면 ms chart control을 설치해 줘야 합니다.
위 링크를 클릭하여 설치를 하시거나 구글에 microsoft chart controls를 검색하여 chart controls를 설치해 줍니다.
1. 다운로드를 살포시 눌러줍니다.
2. MSChart.exe를 체크한 뒤 다음을 누르면 설치 파일이 다운로드 됩니다. 다운로드가 완료된 후 설치를 합니다. 뭐 체크하고 그런거 없으니 그냥 다음 다음 눌러주시면 설치가 완료됩니다.
이제 MSChart가 설치되었으니 확인해볼까요?
3. Visual Studio를 실행한 뒤, Windows Forms Application 프로젝트를 만들어 줍니다.
4. 툴박스의 Data탭을 보면 Chart control이 생긴것을 볼 수 있습니다.
흠 이제 프로그래밍을 해볼까요?
먼저 프로그램 시나리오는 이렇습니다.
프로그램 시나리오
1. 샘플링을 할 원신호의 진폭과 주파수, 그리고 위상을 입력받습니다.(출력 주기는 일일이 입력하기 귀찮으니 원신호의 10주기 정도를 출력해주기로 합시다.)
2. 샘플링 주파수를 입력받습니다.
3. 사용자가 실행 버튼을 누르면 차트컨트롤1에는 원신호를, 차트컨트롤2에 샘플링 결과값을 point로 출력을 합니다.
4. 사용자가 그래프 전환 버튼을 누를 때 마다 차트컨트롤2의 chart type을 point에서 spline으로, spline에서 point로 전환해줍니다.
(뭐 이건 작은 프로그램이니 클래스 다이어그램이랑 유즈케이스 등등은 할 필요가 없을거 같군요.)
그렇다면 어떠한 변수가 필요할까요?
일단 원신호의 진폭과 주파수, 그리고 위상을 입력받을 double형 변수 amp_origin, hz_origin, phase_origin이 필요하겠군요. 또 출력주기를 저장할 double형 변수 out_period와 샘플링 주파수를 입력받을 double형 변수 hz_sample 변수가 필요할 것 같습니다. 또, 차트1에 들어갈 수열을 저장하는 Series형 변수 series1과 차트2에 들어갈 수열을 저장하는 series2변수가 필요할거 같군요. 흠... 변수는 이 정도면 된것 같습니다.
그렇다면 이젠 메쏘드를 한번 생각해보도록하죠. 일단 실행버튼을 클릭했을때 실행되는 이벤트 메쏘드와 전환 버튼을 클릭했을때 실행될 이벤트 메쏘드가 필요하겠군요. 그 다음에는 원신호의 시리즈를 만들어서 차트에 넣어주는 makeSeries가 필요하겠군요. 또 차트2의 차트타입을 바꿔줄 changeChartType 메쏘드가 필요할 것 같습니다.
void run_Click(object sender, EventArgs e); //run 버튼을 클릭했을때 실행되는 이벤트 메쏘드
void trans_Click(object sender, EventArgs e); //trans 버튼을 클릭했을때 실행되는 이벤트 메쏘드
void makeSeries(); //차트1과 2에 넣어줄 수열을 만들어주는 메쏘드
void changeChartType(); //차트2의 차트 타입을 바꿔줄 메쏘드
흠 다 된거 같군요! 이제 프로그램을 만들어봅시다!
어플리케이션 UI
Index
Control Type
Name
Text
Tab Index
1
TextBox
tb_amp_origin
진폭
0
2
TextBox
tb_hz_origin
주파수
1
3
TextBox
tb_phase_origin
위상
2
4
TextBox
tb_hz_sample
샘플링 주파수
3
5
Button
run
실행
4
6
Button
trans
전환
5
7
Chart
chart1
6
8
Chart
chart2
7
저 같은 경우는 TextBox와 Label들의 font size를 20으로 설정하였습니다.
자 그렇다면 위의 표와 그림을 참고하여 toolbox에서 드래그하여 내려놓습니다. Label도 위 그림과 같이 text를 써서 배치해 주시고요.
제 코드를 그대로 쓰실 생각이면 위 표를 보고 Properties(속성)을 설정해주세요.
자 그럼 UI도 만들었으니 코딩을 해서 기능을 넣어 볼까요?
Code
Form1.cs에 가서 코딩을 합시다.
우리가 UI를 만들던 곳은 Form1.cs[Design]입니다. Form1.cs 하위 항목의 Form1을 클릭하면 Form1.cs 파일이 열립니다.
변수 선언
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;//추가해 줍니다. 나머지는 이미 추가되어 있습니다.
namespace MyApp
{
public partial class Form1 : Form
{
double amp_origin, hz_origin, phase_origin, out_period, hz_sample; //클래스에 멤버들을 선언해 줍시다.
Series series1 = new Series();
Series series2 = new Series();//series 변수를 선언하고 변수에 객체를 할당해 줍니다.
double period_constant = 10.0; //몇주기를 출력할지 정해줍니다.
public Form1()
{
InitializeComponent();
}
}
}
자 변수들을 추가해 줍시다!
이제 makeSeries 메쏘드를 만들어 볼까요?
private void makeSeries()
{
chart1.Series.Clear();
chart2.Series.Clear();//chart안에 있는 Series들을 지워줍니다.
series1.Points.Clear();
series2.Points.Clear();//series안에 있는 값들을 지워줍니다.
series1.ChartType = SeriesChartType.Spline;
series2.ChartType = SeriesChartType.Point; //차트타입을 chart1은 spline으로 chart2는 point로 설정해줍니다.
out_period = (1.0/hz_origin) * period_constant;//out_period를 원신호의 10배로 합니다.
for(double i=0 ; i<out_period ; i+= 1.0 / (hz_origin * 10)/*원신호의 series를 만들 것이기 때문에 10배로 샘플링해줍니다.*/)
{
series1.Points.AddXY(i, amp_origin * Math.Cos(2.0 * Math.PI * hz_origin * i + phase_origin));
//series1에 수열을 넣어줍니다. x값은 time(i), y값은 결과값(cos(2*PI*f*t+theta))을 입력합니다.
}
for (double i = 0; i < out_period; i += 1.0 / hz_sample/*원신호를 샘플링 주파수로 샘플링해줍니다.*/)
{
series2.Points.AddXY(i, amp_origin * Math.Cos(2.0 * Math.PI * hz_origin * i + phase_origin));
//series2에 수열을 넣어줍니다. x값은 time(i), y값은 결과값(cos(2*PI*f*t+theta))을 입력합니다.
}
chart1.Series.Add(series1);
chart2.Series.Add(series2);//chart에 각각 series를 추가해 줍니다.
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;
namespace MyApp
{
public partial class Form1 : Form
{
double amp_origin, hz_origin, phase_origin, out_period, hz_sample;
Series series1 = new Series();
Series series2 = new Series();
double period_constant = 10.0; //몇주기를 출력할지 정해줍니다.
private void trans_Click(object sender, EventArgs e)
{
changeChartType();
}
private void run_Click(object sender, EventArgs e)
{
amp_origin = Double.Parse(tb_amp_origin.Text.ToString());
hz_origin = Double.Parse(tb_hz_origin.Text.ToString());
phase_origin = Double.Parse(tb_phase_origin.Text.ToString());
hz_sample = Double.Parse(tb_hz_sample.Text.ToString());//text box로 입력받은 값을 멤버변수에 입력합니다.
makeSeries();//makeSeries를 실행합니다.
}
public Form1()
{
InitializeComponent();
}
private void makeSeries()
{
chart1.Series.Clear();
chart2.Series.Clear();//chart안에 있는 Series들을 지워줍니다.
series1.Points.Clear();
series2.Points.Clear();//series안에 있는 값들을 지워줍니다.
series1.ChartType = SeriesChartType.Spline;
series2.ChartType = SeriesChartType.Point; //차트타입을 chart1은 spline으로 chart2는 point로 설정해줍니다.
out_period = (1.0/hz_origin) * period_constant;//out_period를 원신호의 10배로 합니다.
for(double i=0 ; i<out_period ; i+= 1.0 / (hz_origin * 10)/*원신호의 series를 만들 것이기 때문에 10배로 샘플링해줍니다.*/)
{
series1.Points.AddXY(i, amp_origin * Math.Cos(2.0 * Math.PI * hz_origin * i + phase_origin));
//series1에 수열을 넣어줍니다. x값은 time(i), y값은 결과값(cos(2*PI*f*t+theta))을 입력합니다.
}
for (double i = 0; i < out_period; i += 1.0 / hz_sample/*원신호를 샘플링 주파수로 샘플링해줍니다.*/)
{
series2.Points.AddXY(i, amp_origin * Math.Cos(2.0 * Math.PI * hz_origin * i + phase_origin));
//series2에 수열을 넣어줍니다. x값은 time(i), y값은 결과값(cos(2*PI*f*t+theta))을 입력합니다.
}
chart1.ChartAreas[0].AxisX.Maximum = out_period;
chart1.ChartAreas[0].AxisX.Minimum = 0;
chart1.ChartAreas[0].AxisX.IsStartedFromZero = true;
chart2.ChartAreas[0].AxisX.Maximum = out_period;
chart2.ChartAreas[0].AxisX.Minimum = 0;
chart2.ChartAreas[0].AxisX.IsStartedFromZero = true;
series1.Name = "Original Signal";
series2.Name = "Sampling Signal";//이름을 설정한다.
chart1.Series.Add(series1);
chart2.Series.Add(series2);//chart에 각각 series를 추가해 줍니다.
}
private void changeChartType()
{
if (chart2.Series["Sampling Signal"].ChartType == SeriesChartType.Point)
chart2.Series["Sampling Signal"].ChartType = SeriesChartType.Spline;
else
chart2.Series["Sampling Signal"].ChartType = SeriesChartType.Point;
//chart2의 series의 차트타입이 point면 spline으로 아니면 point로 바꿔줍니다.
}
}
}
그럼 실행했을때 이렇게 되죠.
이렇게 하면 나중에 한 chart에 많은 Series를 넣을때 안 헷갈릴수 있습니다. 저 같은 경우는 어차피 시리즈가 하나니 그냥 디폴트로 하겠습니다. ㅋㅋ 나중에 시간남으시면 해보세요. 맨 처음 켰을때 시리즈 이름을 바꾼생태로 보이게 하려면 굳이 코드로 할 필요 없이 차트의 프로퍼티에서 시리즈를 클릭하여 시리즈 에디터를 실행한 다음 시리즈의 이름을 바꿔주면 됩니다.
왼쪽그림은 차트를 클릭했을때 속성 화면이고 왼쪽 화면에서 시리즈를 클릭한다음 ...버튼을 누르시면 Series Collection Editor를 볼 수 있습니다. Series Collection Editor의 왼쪽을 보시면 시리즈의 인덱스 번호와 이름이 보이죠? 바꾸고자 하는 시리즈를 클릭 한 뒤 오른쪽의 Data의 Name을 바꿔주시면 됩니다. 나중에 시간이 되면 ChartArea와 Series에 대하여 좀 더 자세히 설명하는 포스팅을 올리도록 하겠습니다.
흠 오늘 포스팅은 여기까지 입니다.
아래는 위의 파일들을 올려놓은 link 입니다.
첫번째 링크는 이 프로젝트를 올려놓은 Github링크고요.
두번째 링크는 이 프로젝트를 릴리즈한 실행파일과 프로젝트 압축파일을 올려놓은 구글 드라이브 링크입니다.
코딩을 안해보고 어플리케이션을 그냥 사용만 해보실 분은 구글드라이브 링크를 클릭하셔서 exe파일만 다운받아 실행해 보시면 될 거 같습니다.
아마 실행할때 exe파일을 윈도우 디펜더가 인증받은 앱이 아니라고 경고할 수도 있어요. 그럴땐 그냥 추가사항 누른다음 실행 누르시면 됩니다.(위험한 어플 아니니까요 ㅎㅎ)
주의! .NET Framwork 3.5이상이어야함!
뭐 윈도우 업데이트를 꺼놓으시지만 않으면 거의다 .NET Framework 3.5이상일 겁니다.