FFT和IFFT实现——分别用控制台和MFC实现

  这个学期学了《数字信号处理》,当中最重要的就是快速傅里叶变换(FFT)了,FFT发展到现在已经出现了好几种算法,他们各有优劣,我这里就不一一列出来,因为我不想搞的博客又臭又长。我这里的FFT是FFT-DIT(时域抽取法FFT),算法来自西安电子科技大学出版社的教材《数字信号处理》(高西全,丁美玉),这本书里面有对FFT-DIT适用于计算机算法的详细介绍,我这里也不讲具体算法,要想清楚我的程序为什么是这样子,请自己看教材里面的内容。软件是VS2008。

  这里接直接贴代码。先是用C++控制台的代码:

头文件:

  1 #include <complex>
  2 
  3 using namespace std;
  4 
  5 //蝶形变换
  6 complex<double> *bitRev(complex<double> *x ,int N)
  7 {
  8     complex<double> temp = 0;    //
  9     int n;            //normal index
 10     int r = 0;            //bit-reversed index
 11     int k;            //array 
 12 
 13     for(n = 0; n <= N-2;n++)
 14     {
 15         if( n < r)    //swap samples only for first half of the array
 16         {
 17             temp = x[n];
 18             x[n] = x[r];
 19             x[r] = temp;
 20         }
 21 
 22         k = N/2;    //even n: adds to the previous r
 23                     //odd n: subtract from the previous r      
 24 
 25         while( r>k || r==k)
 26         {
 27             r= r - k;    //keep subtracting reverse carry
 28             k = k/2;    //generated next reverse array
 29         }
 30 
 31         r = r + k;
 32     }
 33 
 34     return x;
 35 }
 36 
 37 //FFT 
 38 complex<double> *FFTdit(complex<double> *a,int N)
 39 {
 40     int t=2;
 41     int m,L = 1 ,L2 ,ir ,ib, it;
 42     complex<double> temp(0,0),j(0,1);
 43     complex<double> *x;
 44     complex<double> W(0,0);                    //twiddle factor
 45     complex<double> pi(3.1415926535898,0);  //define value of pi
 46     int nu;
 47 
 48 
 49     x = bitRev(a,N);
 50 
 51     nu = (int)(log((double)N)/log((double)t));
 52     
 53     for(m = 1; m <= nu; m++)
 54     {
 55         L = L*2;
 56 
 57         L2 = L/2;
 58 
 59         for(ir = 0; ir < L2; ir++)
 60         {
 61             W = exp(-j*(complex<double>)2*pi*(complex<double>)ir/(complex<double>)L);
 62             for(it = ir; it < N;it = it + L)
 63             {
 64                 ib = it + L2;
 65                 temp = x[ib] * W;    
 66                 x[ib] = x[it] - temp;
 67                 x[it] = x[it] + temp;        
 68             }    
 69         }        
 70     }
 71 
 72     return x;
 73 }
 74 
 75 //IFFT--inverse FFT
 76 complex<double> *IFFT(complex<double> *X,int N)
 77 {
 78     int m = (int)ceil(log((double)N)/log(2.0));
 79     int n = 1 << m;
 80     int i;
 81     complex<double> *tempx ;
 82     complex<double> *x = new complex<double>[n];
 83 
 84     for(i = 0; i < n; i++)
 85         X[i].imag(-imag(X[i]));
 86 
 87     tempx = FFTdit(X,n);
 88     
 89     for(i = 0; i < N; i++)
 90     {
 91         if((real<double>(tempx[i]))/n < 1e-14)            
 92             x[i].real(0);
 93         else
 94             x[i].real((real<double>(tempx[i]))/n);
 95 
 96         if((imag<double>(tempx[i]))/n < 1e-14)
 97             x[i].imag(0);
 98         else
 99             x[i].imag(-(imag<double>(tempx[i]))/n);
100     }
101 
102     return x;
103 }
View Code

测试文件:

 1 #include <iostream>
 2 #include "DIT_FFT.h"
 3 
 4 using namespace std;
 5 
 6 void main()
 7 {
 8     int i,N;
 9     complex<double> *p ,*x;
10     complex<double> a[16]={0,1,2,3,4,5,6,7,9,10,11,12,13,14,15};
11 
12     N = sizeof(a)/sizeof(complex<double>);
13 
14     p = FFTdit(a,N);
15 
16     cout << "经过FFT之后的排列:" << endl;
17     for(i = 0; i < N; i++)
18         cout << *(p+i) << endl;
19 
20     cout << "经过abs(FFT)之后的排列:" << endl;
21     for(i = 0; i < N; i++)
22         cout << abs(*(p+i))/(N/2) << endl;
23     
24     x = IFFT(p,N);
25     cout << "经过IFFT之后的排列:" << endl;
26     for(i = 0; i < N; i++)
27         cout << *(x+i) << endl;
28 
29     cout << "x取绝对值" << endl;
30     for(i = 0; i < N; i++)
31         cout << abs(*(x+i)) << endl;
32 
33     cout << endl << endl;
34     cout << "522/400 = ";
35     cout << (double)(522.0/400.0) << endl;
36 
37 }
View Code

  上面的内容可直接拷贝出来直接用,但是要注意我用的是VS2008。

  其实MFC的实现FFT和控制台的没什么不同,只是MFC要进行一些操作而已。

首先我想要在在对话框中用FFT进行信号的幅频分析显示,在对话框中加入Picture控件用于显示,再加入一个Button控件用于打开文件获取要分析的数据。然后利用类视图中选中

相应的类,比如我的是FFTDlg类右键添加函数,添加函数时没有的类型就自己手动添加。如下图:

 1 // FFT前的蝶形变换
 2 complex<double>* CFFTDlg::bitRev(complex<double>* x, int N)
 3 {
 4     complex<double> temp = 0;    //
 5     int n;            //normal index
 6     int r = 0;            //bit-reversed index
 7     int k;            //array 
 8     
 9     for(n = 0; n <= N-2;n++)
10     {
11         if( n < r)    //swap samples only for first half of the array
12         {
13             temp = x[n];
14             x[n] = x[r];
15             x[r] = temp;
16         }
17 
18         k = N/2;    //even n: adds to the previous r
19                     //odd n: subtract from the previous r      
20 
21         while( r>k || r==k)
22         {
23             r= r - k;    //keep subtracting reverse carry
24             k = k/2;    //generated next reverse array
25         }
26 
27         r = r + k;
28     }
29 
30     return x;
31 }
View Code
 1 // FFT-DIT变换
 2 complex<double>* CFFTDlg::FFTdit(complex<double>* a, int N)
 3 {
 4     int t=2;
 5     int m,L = 1 ,L2 ,ir ,ib, it;
 6     complex<double> temp(0,0),j(0,1);
 7     complex<double> *x;
 8     complex<double> W(0,0);                    //twiddle factor
 9     complex<double> pi(3.1415926535898,0);  //define value of pi
10     int nu;
11 
12     x = bitRev(a,N);
13 
14     nu = (int)(log((double)N)/log((double)t));
15     
16     for(m = 1; m <= nu; m++)
17     {
18         L = L*2;
19 
20         L2 = L/2;
21 
22         for(ir = 0; ir < L2; ir++)
23         {
24             W = exp(-j*(complex<double>)2*pi*(complex<double>)ir/(complex<double>)L);
25             for(it = ir; it < N;it = it + L)
26             {
27                 ib = it + L2;
28                 temp = x[ib] * W;    
29                 x[ib] = x[it] - temp;
30                 x[it] = x[it] + temp;        
31             }    
32         }        
33     }
34 
35     return x;
36 }
View Code

头文件所需:

 1 #include <complex>
 2 
 3 using namespace std;
 4 
 5 
 6 
 7 
 8 
 9 
10 complex<double> *cydata;
11 complex<double> *dataFFT;
View Code

下面对Button控件添加消息处理函数:

 1 void CFFTDlg::OnBnClickedButtonInputfile()
 2 {
 3     // TODO: 在此添加控件通知处理程序代码
 4     CFileDialog dlg(TRUE); // 定义一个文件对话框变量
 5     if(dlg.DoModal()==IDOK)    
 6     { 
 7     //成功打开文件时先销毁链表,再获取链表
 8         xdata.clearList();
 9         ydata.clearList();
10 //取得文件路径及文件名
11         CString m_FilePath = dlg.GetPathName(); 
12         CStdioFile File; 
13 //以读模式打开文本文件
14         File.Open(m_FilePath,CFile::modeRead); 
15         CString FileData;        //定义一个CString变量作为缓冲区
16         File.ReadString(FileData);//读取第一行数据,第一行数据为坐标点数据
17 //字符数组,如果出现"TempStr2 was corrupted",这时要把TempStr2的内存再改大一些 
18         char TempStr1[20]; 
19         char TempStr2[20]; 
20 
21         memset(TempStr1,'\0',10); 
22         memset(TempStr2,'\0',10); 
23         sscanf(FileData,"%s",TempStr1); 
24         m_PointNum = atoi(TempStr1);   // 获取坐标点个数
25 //初始化cydata
26         cydata = new complex<double>[m_PointNum];
27 //逐行读取坐标数据     
28         for (int i = 0;i<m_PointNum;i++) 
29         { 
30             File.ReadString(FileData); 
31             sscanf(FileData,"%s %s",TempStr1,TempStr2); 
32             if(i < m_PointNum/2)
33                 xdata.insertAt(i,atof(TempStr1));
34             //ydata.insertAt(i,atof(TempStr2));
35             *(cydata+i) = atof(TempStr2);
36         } 
37 //进行FFT变化
38         dataFFT =  FFTdit(cydata,m_PointNum);
39 
40         for(int i = 0;i <m_PointNum/2; i++)
41         {
42             ydata.insertAt(i,abs(*(dataFFT+i))/(m_PointNum/2));
43         }
44 //删除内存
45         delete cydata;
46    } 
47 }
View Code

其中,xdata和 ydata为我自己写的模板类arrayListType,这个类的模板也是从我之前上课练习的控制台代码拿出来的,因为我里面有添加一些我需要的函数。这里也贴出来,不完善,但是能用,不会报错。

  1 #include <cassert>
  2 //#include <iostream>
  3 
  4 //using namespace std;
  5 
  6 template<class elemType>
  7 class arrayListType
  8 {
  9 public:
 10     const arrayListType<elemType>&
 11                     operator=(const arrayListType<elemType>&);
 12         //Overload the assigment operator
 13 
 14     bool isEmpty();
 15         //Function to determine whether the list is empty
 16         //Postcondition:Return true if the list is empty;
 17         //                otherwise,returns false.
 18 
 19     bool isFull();
 20         //Function to determine whether the list is full.
 21         //Postcondition:Return true if the list is empty
 22         //                otherwise,return false.
 23 
 24     int listSize();
 25         //Function to determine the number of elements in
 26         //the list.
 27         //Postcondition:Return the value of length.
 28 
 29     int maxListSize();
 30         //Function to determine the size of the lise.
 31         //Postcondition:Returns the value of maxSize.
 32 
 33     void print();
 34         //Function to output the elements of the list.
 35         //Postcondition:The elements of the list are output
 36         //                on the standard output device.
 37 
 38     bool isItemAtEqual(int location,const elemType& item);
 39         //Function to determine whether the item is the
 40         //same as the item in the list at the position
 41         //specified by location.
 42         //Postcondition:Returns true if the list[location]
 43         //                is the same as the item ;otherwise
 44         //                returns false.
 45 
 46     void insertAt(int location,const elemType& insertItem);
 47         //Function to insert an item in the list at the 
 48         //position specified by location.The item to be
 49         //inserted is passed as a parameter to the function
 50         //Postcondition:Starting at location ,the elements 
 51         //                of the list are shifted down,
 52         //                list[location] = insertItem; ,
 53         //                and length++;
 54         //                If the list is full or location is
 55         //                out of range ,an appropriate message
 56         //                is displayed.
 57 
 58     void insertEnd(const elemType& insertItem);
 59         //Function to insert an item at the of the list.
 60         //The parameter insertItem specified the item to 
 61         //be inserted.
 62         //Postcondition:list[length] = insetItem; and length++;
 63         //    If the list is full, an appropriate message is
 64         //    displayed.
 65 
 66     void removeAt(int location);
 67         //Function to remove the item from the list at 
 68         //the position specified by location.
 69         //Postcondition:The list elememt at list[location]
 70         //                is removed.and length is determined by 1.
 71         //    If location is out of range ,an appropriate message
 72         //    is displayed.
 73     
 74     //void retrieveAt(int location,elemType& retItem);
 75     //    //Function to retrieve the element from the list at
 76     //    //the position specified by location .
 77     //    //Postcondition:retItem = list[location]
 78     //    //                If location is out of range,an appropiate
 79     //    //                message is displayed.
 80 
 81     elemType retrieveAt(int location);
 82         //Function to retrieve the element from the list at
 83         //the position specified by location .
 84         //Postcondition:list[location]
 85         //                If location is out of range,an appropiate
 86         //                message is displayed.
 87 
 88     void replaceAt(int location, const elemType& repItem);
 89         //Function to replace the element in the list at
 90         //the position specified by location .The item to be
 91         //Postcondition:list[location] = repItem
 92         //                If location is out of range ,an appropriate
 93         //                message is displayed.
 94 
 95     void clearList();
 96         //Function to remove all the elements from the list.
 97         //After this operation ,the size of the list is zero.
 98 
 99     int seqSearch(const elemType& item);
100         //Function to search the list for a given item
101         //Postcondition:If the item is found ,teturns the 
102         //                location in the array where the 
103         //                item is found ;otherwise return -1.
104 
105     void insert(const elemType& insertItem);
106         //Function to insert the item specified by the
107         //parameter insertItem at the end of the list.
108         //However,first the list is searched to see whether
109         //the item to be inserted is already in the list.
110         //Postcondition:list[length] = insertItem;and length++
111         //    If the item is already in the list or the list is
112         //    full,an appropriate message is displayed.
113 
114     void remove(const elemType& removeItem);
115         //Function to remove an item from the list.The
116         //parameter removeItem specified the item to be
117         //removed.
118         //Postcondition:If removeItem is found in the list
119         //                it is removed from the list and
120         //                length is decremented by one.
121 
122     elemType maxValue();
123         //Function to determne the max value in the list
124         //Postcondition:Return the max value int the list
125 
126     elemType minValue();
127         //Function to determne the min value in the list
128         //Postcondition:Return the min value int the list
129 
130     arrayListType(int size = 10000);
131         //constructor 
132         //Creates an array of the size specified  by parameter 
133         // size.The default array size is 100.
134         //Postcondition:The list points to the array;
135         //                length = 0;and maxSize = size
136 
137     arrayListType(const arrayListType<elemType>& otherList);
138         //copy constructor 
139 
140     ~arrayListType();
141         //destructor 
142         //Deallocates the memory occuped by the array
143 
144 protected:
145     elemType *list;        //array to hold the list
146                         //elements
147     int length;            //variable to store the length of 
148                         //the list
149     int maxSize;        //variable to store the maximum size
150                         //of the list.
151 };
152 
153 
154 template<class elemType>
155 bool arrayListType<elemType>::isEmpty()
156 {
157     return(length == 0);
158 }
159 
160 template<class elemType>
161 bool arrayListType<elemType>::isFull()
162 {
163     return(length == maxSize);
164 }
165 
166 template<class elemType>
167 int arrayListType<elemType>::listSize()
168 {
169     return length;
170 }
171 
172 template<class elemType>
173 int arrayListType<elemType>::maxListSize()
174 {
175     return maxSize;
176 }
177 
178 template<class elemType>
179 void arrayListType<elemType>::print()
180 {
181     for(int i = 0; i < length; i++);
182         //cout << list[i] << " " << endl;
183 }
184 
185 template<class elemType>
186 bool arrayListType<elemType>::isItemAtEqual
187                             (int location, const elemType &item)
188 {
189     return(list[location] == item);
190 }
191 
192 template<class elemType>
193 void arrayListType<elemType>::insertAt
194                             (int location, const elemType &insertItem)
195 {
196     if(location < 0 || location >= maxSize);
197     /*    cerr << "The position of the item to be inserted"
198              << " is out of range." << endl;*/
199     else
200         if(length >= maxSize)
201         {
202             //cout << "Can not insert in a full list" << endl;
203             maxSize = length;    //我添加的
204         }
205         else
206         {
207             for(int i = length; i > location; i--)
208                 list[i] = list[i-1];
209 
210             list[location] = insertItem;
211 
212             length++;
213         }
214 }
215 
216 template<class elemType>
217 void arrayListType<elemType>::insertEnd(const elemType &insertItem)
218 {
219     if(length >= maxSize);
220         //cerr << "Cannot insert in a full list" << endl;
221     else
222     {
223         list[length] = inserItem;
224         length++;
225     }
226 }
227 
228 template<class elemType>
229 void arrayListType<elemType>::removeAt(int location)
230 {
231     if(location < 0 || location >= length);
232     /*    cerr << "The location of the item to be removed "
233              << "is out of range." << endl;*/
234     else
235     {
236         for(int i = location; i < length; i++)
237             list[i] = list[i+1];
238 
239         length--;
240     }
241 }
242 
243 //template<class elemType>
244 //void arrayListType<elemType>::retrieveAt(int location, elemType &retItem)
245 //{
246 //    if(location <0 || location >= length)
247 //        cerr << "The location of the item to be retrieved is "
248 //             << "out of range." << endl;
249 //    else
250 //        retItem = list[location];
251 //}
252 
253 
254 template<class elemType>
255 elemType arrayListType<elemType>::retrieveAt(int location)
256 {
257     if(location <0 || location >= length);
258     /*    cerr << "The location of the item to be retrieved is "
259              << "out of range." << endl;*/
260     else
261         return list[location];
262 }
263 
264 template<class elemType>
265 void arrayListType<elemType>::replaceAt(int location, const elemType &repItem)
266 {
267     if(location < 0 || location >= length);
268     /*    cerr << "The location of the item to be retrieved is "
269              << "out of range." << endl;*/
270     else
271         list[location] = repItem;
272 }
273 
274 template<class elemType>
275 void arrayListType<elemType>::clearList()
276 {
277     length = 0;
278 }
279 
280 template<class elemType>
281 arrayListType<elemType>::arrayListType(int size)
282 {
283     if(size < 0)
284     {
285     /*    cerr << "The array size must be positive.Creating " 
286              << "an array of size 100" << endl;*/
287     ;}
288     else
289         maxSize = 10000;
290 
291     length = 0;
292 
293     list = new elemType[maxSize];
294     assert(list != NULL);
295 }
296 
297 template<class elemType>
298 arrayListType<elemType>::~arrayListType()
299 {
300     delete []list;
301 }
302 
303 template<class elemType>
304 arrayListType<elemType>::arrayListType
305                 (const arrayListType<elemType> &otherList)
306 {
307     maxSize = otherList.maxSize;
308     length = otherList.length;
309     list = new elemType[maxSize];
310     assert(list != NULL);
311 
312     for(int i = 0; i < length; i++)
313         list[i] = otherList.list[i];
314 }
315 
316 template<class elemType>
317 const arrayListType<elemType>&
318                     arrayListType<elemType>::operator =
319                     (const arrayListType<elemType> &otherList)
320 {
321     if(this != &otherList)
322     {
323         delete [] list;
324         maxSize = otherList.maxSize;
325         length = otherList.length;
326 
327         list = new elemType[maxSize];
328         assert(list != NULL);
329 
330         for(int i= 0; i < lenght; i++)
331             list[i] = otherList.list[i];
332     }
333 
334     return *this;
335 }
336 
337 
338 template<class elemType>
339 int arrayListType<elemType>::seqSearch(const elemType &item)
340 {
341     int loc;
342     bool found = false;
343 
344     for(loc = 0;loc < length; loc++)
345         if(list[loc] == item)
346         {
347             found = true;
348             break;
349         }
350 
351         if(found)
352             return loc;
353         else
354             return -1;
355 }
356 
357 template<class elemType>
358 void arrayListType<elemType>::insert(const elemType& insertItem)
359 {
360     int loc;
361 
362     if(length == 0)
363         list[length++] = insertItem;
364     else
365         if(length == maxSize);
366             //cerr << "Cannot insert in a full list." << endl;
367         else
368         {
369             loc = seqSearch(insertTtem);
370             if(loc == -1)
371                 list[length++] = insertItem;
372         /*    else
373                 cerr << "The item to be inserted is already in " 
374                      << "list .No duplicates are allowed." << endl;*/
375         }
376 }
377 
378 template<class elemType>
379 void arrayListType<elemType>::remove(const elemType& removeItem)
380 {
381     int loc;
382 
383     if(length == 0);
384         //cerr << "Cannot delete from an empty list." << endl;
385     else
386         loc = seqSearch(removeItem);
387 
388     if(loc != -1)
389         removeAt(loc);
390 /*    else
391         co*/ut << "The item to be deleted is not in the list." << endl;
392 }
393 
394 template<class elemType>
395 elemType arrayListType<elemType>::maxValue()
396 {
397     elemType temp;
398 
399     temp = list[0];
400     for(int i = 0; i < length; i++)
401         if(temp < list[i])
402             temp = list[i];
403 
404     return temp;
405 }
406 
407 template<class elemType>
408 elemType arrayListType<elemType>::minValue()
409 {
410     elemType temp;
411 
412     temp = list[0];
413     for(int i = 0; i < length; i++)
414         if(temp > list[i])
415             temp = list[i];
416 
417     return temp;
418 }
View Code

记得把要用的头文件和变量添加到自己对话框类的头文件中

1 #include "arrayListType.h"
2 
3 
4     
5 CArray<CPoint,CPoint> m_PointArray; // 用于记录坐标点数据
6 int m_PointNum;   // 用于记录坐标点个数,在视图类构造函数中初始化为0。
7 
8 arrayListType<double> xdata;
9 arrayListType<double> ydata;
View Code
下面就是绘图的函数,有点乱。首先在对话框中绘图要在OnPaint当中,如果没有出现这个函数,请在属性框中选择“消息”项,再选择WM_PAINT。图如下:
在OnPaint中的代码如下:
 1 void CFFTDlg::OnPaint()
 2 {
 3     CPaintDC dc(this); // device context for painting
 4     // TODO: 在此处添加消息处理程序代码
 5     CRect rc;
 6     CWnd* pWnd;
 7     pWnd = GetDlgItem(IDC_STATIC_DISFFT);    //IDC_STATIC_DISFFT是Picture控件的ID
 8     pWnd->GetWindowRect(rc);    //获取Picture控件的方框大小
 9     CDC* pDC = pWnd->GetDC();    //获取DC
10 //绘图函数,
11     DrawGrid(rc.Width(), rc.Height(), 1, pDC);
12     // 不为绘图消息调用 CDialog::OnPaint()
13 }
View Code

上面代码中函数DrawGrid用于双缓存绘图,其中的flag不用理会,这是我有别的用处加进去的,不用对flag进行处理。代码如下:

 1 void CFFTDlg::DrawGrid(int width, int height, int flag, CDC* pDC)
 2 { 
 3     CPen newPen;       // 用于创建新画笔   
 4     CPen *pOldPen;     // 用于存放旧画笔   
 5     CBrush newBrush;   // 用于创建新画刷   
 6     CBrush *pOldBrush; // 用于存放旧画刷   
 7 
 8     CRect rectPicture;
 9     CWnd* pWnd;
10     pWnd = GetDlgItem(IDC_STATIC_DISFFT); //IDC_STATIC_DISFFT是Picture控件的ID
11     pWnd->GetWindowRect(rectPicture);
12 
13 //这里是从网上摘下,自己改的
14 //*******************************************************************    
15     CDC   MemDC;   //首先定义一个显示设备对象   
16     CBitmap   MemBitmap;//定义一个位图对象    
17   //随后建立与屏幕显示兼容的内存显示设备   
18     MemDC.CreateCompatibleDC(NULL);   
19   //这时还不能绘图,因为没有地方画   ^_^   
20   //下面建立一个与屏幕显示兼容的位图,至于位图的大小嘛,可以用窗口的大小   
21     MemBitmap.CreateCompatibleBitmap(pDC,width,height);   
22       
23   //将位图选入到内存显示设备中   
24   //只有选入了位图的内存显示设备才有地方绘图,画到指定的位图上   
25     CBitmap   *pOldBit=MemDC.SelectObject(&MemBitmap);   
26     
27   //先用背景色将位图清除干净,这里我用的是白色作为背景   
28   //你也可以用自己应该用的颜色   
29     MemDC.FillSolidRect(0,0,width,height,RGB(0,0,0));   
30 //*******************************************************************
31 
32     // 创建黑色新画刷   
33     /*if(nreverse == 0)*/
34         newBrush.CreateSolidBrush(RGB(0,0,0)); 
35     /*else
36         newBrush.CreateSolidBrush(RGB(255,255,255)); */
37     // 选择新画刷,并将旧画刷的指针保存到pOldBrush   
38     pOldBrush = MemDC.SelectObject(&newBrush);   
39     // 以黑色画刷为绘图控件填充黑色,形成黑色背景   
40     MemDC.Rectangle(rectPicture);   
41     // 恢复旧画刷   
42     MemDC.SelectObject(pOldBrush);   
43     // 删除新画刷   
44     newBrush.DeleteObject();   
45   
46     // 创建实心画笔,粗度为1,颜色为绿色   
47     newPen.CreatePen(PS_SOLID, 1, RGB(0,255,0));   
48     // 选择新画笔,并将旧画笔的指针保存到pOldPen   
49     pOldPen = MemDC.SelectObject(&newPen);   
50  
51 //这两个函数是自己添加的
52 //*******************************************************************
53 //绘制网格
54     DrawPic(width, height, &MemDC);
55 //绘制图形
56     Plot(width, height,1,rectPicture,&MemDC);
57 //*******************************************************************
58        
59     MemDC.SelectObject(pOldPen);   // 恢复旧画笔   
60     newPen.DeleteObject();   // 删除新画笔   
61   //*******************************************************************
62 //将内存中的图拷贝到屏幕上进行显示    
63     pDC->BitBlt(0,0,width,height,&MemDC,0,0,SRCCOPY);  
64     
65   //绘图完成后的清理   
66     MemBitmap.DeleteObject();   
67     MemDC.DeleteDC();  
68 }
View Code

上面的DrawPic用于绘制网格和边框以及刻度,不需要刻度的,请自己修改,这是没来得及完善的作品。DrawPic的代码如下:

  1 oid CFFTDlg::DrawPic(int width, int height, CDC* pDC)
  2 {
  3     //边框到窗口的距离
  4     int g = 10;    
  5 //绘制网格的参数
  6     int x = g;
  7     int y = g;
  8     int h = height - 2*g;
  9     int w = width - 2*g;
 10     int wg = (int)(w/10);
 11     int hg = (int)(h/6);
 12     int sg = (width - 2*g)/50;
 13 
 14     int wi[10];
 15     int hi[5];
 16     int i;
 17 //竖线
 18     switch(w%10)
 19     {
 20     case 0:
 21         for(i = 0; i < 10; i++)
 22             wi[i] = wg;
 23         break;
 24     case 1:
 25         for(i = 0; i < 10; i++)
 26         {
 27             if(i == 0)
 28                 wi[i] = wg+1;
 29             else
 30                 wi[i] = wg;
 31         }
 32         break;
 33     case 2:
 34         for(i = 0; i < 10; i++)
 35         {
 36             if(i <= 1)
 37                 wi[i] = wg+1;
 38             else
 39                 wi[i] = wg;
 40         }
 41         break;
 42     case 3:
 43         for(i = 0; i < 10; i++)
 44         {
 45             if(i <= 2)
 46                 wi[i] = wg+1;
 47             else
 48                 wi[i] = wg;
 49         }
 50         break;
 51     case 4:
 52         for(i = 0; i < 10; i++)
 53         {
 54             if(i <= 3)
 55                 wi[i] = wg+1;
 56             else
 57                 wi[i] = wg;
 58         }
 59         break;
 60     case 5:
 61         for(i = 0; i < 10; i++)
 62         {
 63             if(i <= 4)
 64                 wi[i] = wg+1;
 65             else
 66                 wi[i] = wg;
 67         }
 68         break;
 69     case 6:
 70         for(i = 0; i < 10; i++)
 71         {
 72             if(i <= 5)
 73                 wi[i] = wg+1;
 74             else
 75                 wi[i] = wg;
 76         }
 77         break;
 78     case 7:
 79         for(i = 0; i < 10; i++)
 80         {
 81             if(i <= 6)
 82                 wi[i] = wg+1;
 83             else
 84                 wi[i] = wg;
 85         }
 86         break;
 87     case 8:
 88         for(i = 0; i < 10; i++)
 89         {
 90             if(i <= 7)
 91                 wi[i] = wg+1;
 92             else
 93                 wi[i] = wg;
 94         }
 95         break;
 96     case 9:
 97         for(i = 0; i < 10; i++)
 98         {
 99             if(i <= 8)
100                 wi[i] = wg+1;
101             else
102                 wi[i] = wg;
103         }
104         break;
105     }
106 //横线
107     switch(h%6)
108     {
109     case 0:
110         for(i = 0; i < 5; i++)
111             hi[i] = hg;
112         break;
113     case 1:
114         for(i = 0; i < 5; i++)
115         {
116             if(i == 0)
117                 hi[i] = hg+1;
118             else
119                 hi[i] = hg;
120         }
121         break;
122     case 2:
123         for(i = 0; i < 5; i++)
124         {
125             if(i <= 1)
126                 hi[i] = hg+1;
127             else
128                 hi[i] = hg;
129         }
130         break;
131     case 3:
132         for(i = 0; i < 5; i++)
133         {
134             if(i <= 2)
135                 hi[i] = hg+1;
136             else
137                 hi[i] = hg;
138         }
139         break;
140     case 4:
141         for(i = 0; i < 5; i++)
142         {
143             if(i <= 3)
144                 hi[i] = hg+1;
145             else
146                 hi[i] = hg;
147         }
148         break;
149     case 5:
150         for(i = 0; i < 5; i++)
151         {
152             if(i <= 4)
153                 hi[i] = hg+1;
154             else
155                 hi[i] = hg;
156         }
157         break;
158 
159     }
160 
161 //刻度
162     int temp1;
163     int temp2;
164     int j = 0;
165     int gi[50];
166     for(i = 0; i < 10; i++)
167     {
168         temp1 = wi[i]/5;
169         temp2 = wi[i]%5;
170         switch(temp2)
171         {
172         case 0:
173             for(j = 0; j < 5; j++)
174                 gi[5*i+j] = temp1;
175             break;
176         case 1:
177             for(j = 0; j < 5; j++)
178             {
179                 if(j == 0)
180                     gi[5*i+j] = temp1 + 1;
181                 else
182                     gi[5*i+j] = temp1;
183             }
184             break;
185         case 2:
186             for(j = 0; j < 5; j++)
187             {
188                 if(j <= 1)
189                     gi[5*i+j] = temp1 + 1;
190                 else
191                     gi[5*i+j] = temp1;
192             }
193             break;
194         case 3:
195             for(j = 0; j < 5; j++)
196             {
197                 if(j <= 2)
198                     gi[5*i+j] = temp1 + 1;
199                 else
200                     gi[5*i+j] = temp1;
201             }
202             break;
203         case 4:
204             for(j = 0; j < 5; j++)
205             {
206                 if(j <= 3)
207                     gi[5*i+j] = temp1 + 1;
208                 else
209                     gi[5*i+j] = temp1;
210             }
211             break;
212         }
213     }
214 
215 
216     CPen newPen;
217     CPen* pOldPen;
218 
219     //if(flag == 0)
220     //    newPen.CreatePen(PS_DOT,1,RGB(128,128,128));    //网格的颜色为灰色
221     //else
222         newPen.CreatePen(PS_DOT,1,RGB(169,169,169));    //网格的颜色为深灰色
223     pOldPen = pDC->SelectObject(&newPen);        //选择画笔
224 
225 //绘制竖线
226     for(int i = 0; i < 9 ; i++)
227     {
228         x = x + wi[i];
229         y = y + hi[i];
230         //绘制竖虚线
231         pDC->MoveTo(x,g);
232         pDC->LineTo(x,height-g);
233         //绘制横虚线
234         if(i < 5 && i != 2)
235         {
236             pDC->MoveTo(g,y);
237             pDC->LineTo(width-g,y);
238         }
239     }
240 
241 //销毁画笔
242     newPen.DeleteObject();
243 
244     /*if(flag == 0)*/
245         newPen.CreatePen(PS_SOLID,1,RGB(255,255,255));    //定义画笔,颜色为白色
246     //else
247     //    newPen.CreatePen(PS_SOLID,1,RGB(0,0,0));    //定义画笔,颜色为黑色
248         pDC->SelectObject(newPen);    //选择新画笔
249 
250 //绘制坐标轴的边框
251     x = g;
252     y = g;
253     pDC->MoveTo(g,g);
254     pDC->LineTo(width-g,g);
255     pDC->MoveTo(width-g,g);
256     pDC->LineTo(width-g,height-g);
257     pDC->MoveTo(width-g,height-g);
258     pDC->LineTo(g,height-g);
259     pDC->MoveTo(g,height-g);
260     pDC->LineTo(g,g);
261 //绘制x轴轴线
262     pDC->MoveTo(x,y+3*hg);
263     pDC->LineTo(width-g,y+3*hg);
264 
265 //绘制刻度
266     int x1 = g;
267     int yv = y+3*hg;
268     for(int i = 0; i < 50; i++)
269     {
270         x1 = x1 + gi[i];
271         pDC->MoveTo(x1,yv+3);
272         pDC->LineTo(x1,yv-2);
273     }
274 
275     pDC->SelectObject(pOldPen);
276     newPen.DeleteObject();
277 }
View Code

Plot用于绘制用FFT之后的数据绘制频谱,代码如下:

 1 // 绘图接口函数,width绘图区域的长度,height为绘图区域的高,flag为标志位,pdc为指针
 2 void CFFTDlg::Plot(int width, int height, int flag, CRect rc, CDC* pdc)
 3 {
 4     CPen newPen;
 5     CPen* pOldPen;
 6     newPen.CreatePen(PS_SOLID,2,RGB(178,34,34));    //绘制曲线的颜色,火砖色
 7     pOldPen = pdc->SelectObject(&newPen);
 8     
 9     int w = width - 20;
10     int h = height - 20;
11 
12     int x = 20;
13     int y = 20;
14     int i;
15 
16     double W = double(w*100)/100.0;
17     double H = double(h*100)/100.0;
18 
19     if(ydata.minValue()< 0.0)
20         pdc->MoveTo(10,(int)(height/2));
21     else
22         pdc->MoveTo(10,height-10);
23     for(i = 0; i < m_PointNum/2;i++)
24     {    
25         if(ydata.minValue() < 0.0)
26         {
27             x = int(10+2*W*xdata.retrieveAt(i)/xdata.maxValue());
28             y = int((int)(height/2)-H/2*ydata.retrieveAt(i)/ydata.maxValue());
29         }
30         else
31         {
32             x = int(10+W*xdata.retrieveAt(i)/xdata.maxValue());
33             y = int(height - 10 -H*ydata.retrieveAt(i)/ydata.maxValue());
34         }
35         pdc->LineTo(x,y);
36     }
37 
38     pdc->SelectObject(pOldPen);
39     newPen.DeleteObject();
40 }
View Code

我自己的程序是可以用的,上面的内容应该没有完全,自己拷贝拿来用的时候注意是否初始化了变量,是否添加了函数等,不要盲目直接用,最后一直报错却说我的代码不行,鄙视这种人!

最后的贴出粗糙的效果图,我还没有全部完成,比如图中还没有添加刻度文字等等。

 

上面三个是要用的函数,自己看。

转载于:https://www.cnblogs.com/WastonLiang/archive/2013/05/21/3090867.html

使用MFC在VS2013编写的数字图象处理软件,能够实现相当强大的功能。 BMP格式读取 保存 DFT FFT 直方图 色调均化 缩放 模糊 锐化 滤镜 形态学处理 曲线 裁剪 灰度图 彩色图 自动阈值 等等...除此之外还有很多其他小功能... 建议使用VS2013打开!!!核心代码在Bmp.cpp中!!! 更新文档: 2014年6月18日更新说明: 这次应该是上交的最后一次作业了,在今日的展示结束之后总体情况还好,但是发现了几个问题。 首先是这个程序是在win8环境下设计的,所以程序的一些大小参数以及按钮图片的位置参数是适合在win8的环境下操作,在设计报告中使用的操作系统也是win8。 而如果将该程序移动至win7系统上操作的话可以在大小与位置上会出现一些偏差,所以推荐将该程序在win8系统下运行,如果没有win8系统但是想重装的话可以找我。 然后本次更新的内容就是对设计报告中的要求的一些补充,比如图片的裁剪功能,还有一些照旧的BUG修复了。 关于这个裁剪功能,在程序中的图像裁剪中有一个说明按钮,在设计报告中有提到怎么使用的,所以在这里就不一一说明了,其实就跟在PS上用裁剪差不多,很容易用的。 关于设计的感想也写在了设计报告上了- -,这里也就不多说了。 好了这个程序算是最终完成了,撒花!师姐辛苦了~!!!!! ================================================================================================================================================================ 2014年5月13日更新说明: 这次的更新比较少,主要就是自动阈值分割图像方面的更新。 实现该操作的函数依然放在Bmp.cpp里面,里面一共使用了三种方式来决定自动阈值。 其中一种是“大津法”,函数是“OtsuThreshold”,该函数最后会返回一个阈值,该阈值就是大津法得出的阈值,具体实现方式可以在cpp文件中查看。 还有一种方法就是“迭代法”,函数是“IterationThreshold”,该函数最后会返回一个阈值,该阈值就是迭代法得出的阈值,具体实现方式可以在cpp文件中查看。 前两种方法的实现方法都如老师在PPT上所说的一样,而且运算起来非常快,基本可以说是瞬时得出。 而第三种方法是我自己写的一个方法,叫做“对半分”法,函数是“HalfCutThreshold”,该函数最后会返回一个阈值,该阈值就是对半分得出的阈值,具体实现方式可以在cpp文件中查看。 其原理就是计算出一个阈值,使到阈值处理后图片的黑色像素与白色像素的数量相等或者最接近,也就是把图片按黑白像素对半分的方法来对图像进行分割。 关于程序的使用方法,可以在鼠标右键菜单中选择“调整”->“阈值”->“高级阈值”来打开高级阈值处理的对话框。 打开对话框后,默认为最直接的自己首选阈值的方法,可以通过鼠标的左键拖动直方图中的绿色竖线来调整需要设定的阈值大小,同时右边会有该图片的预览,可以很方便操作。 如果需要使用自动阈值分割,可以在阈值方式中更改,一旦选择了“直接阈值”以外的阈值方式,程序便会自动用所选择的方法帮你计算出一个阈值,同时在直方图上会显示出该阈值的位置,还有该阈值的大小,同时预览图片也会立即更新。 值得注意的是,当你选择了自动阈值的时候,你不能再通过鼠标左键在直方图上手动调整阈值大小了哦,这个时候你只需要将阈值方式调回“直接阈值”即可重新自己调整! 除了有关作业的更新之外,这次更新还调整了图片备份的内存优化,加上了使用磁盘作为备份的空间,不过这些作为使用者的话是不需要怎么注意的嗯嗯,尽情使用即可! 最后,再次谢谢师姐能够读完这个文档,如果还有什么问题的话就联系我吧,联系方式就在软件中了欢迎点击- -,谢谢! ================================================================================================================================================================ 2014年5月6日更新说明: 这次的主要更新是形态学处理的部分,也就是膨胀、腐蚀、开与闭操作。 实现函数依然是放在Bmp.cpp这个文件里面。名字为Morphology的函数就是该形态学操作的函数。 可以通过在函数中调入不同的参数与设置使到一个函数同时实现膨胀与腐蚀的功能,而开与闭的功能只需要连续调用两次函数,并且参数不同就行了,使用非常简单。 然后就是软件的使用部分,软件的位置依然是放在Release文件夹里面,而形态学处理的按钮在“右键-调整-形态学处理-形态学运算”中,单击后就会出现形态学运算的对话框。 在对话框中会看到一个9*9的结构元素方阵,可以通过使用鼠标左键点击来改变结构元素的形状,双击鼠标为还原结构元素。 设定好结构元素后可以选择操作的四种方式,选择后便会得到处理后的图像了,十分方便。 当然,如果你的图片是彩色的,这个处理也是支持的,因为我的实现函数中把“与”操作改为“取最小值”,“或”操作改为“取最大值”,这个改动在对于二值图像的处理是一样效果的,但是也使到形态学处理适用于彩色图像了。 如果你想把彩色图片或者灰度图片二值化后在进行形态学处理,可以勾选对话框中的“二值化”选项。 值得一提的是,这个二值化是对每一层的颜色单独二值化,如果想要实现阈值效果,可以直接使用阈值功能或者转化为灰度图之后再使用对话框内的二值化,效果是一样的。 除此之外还有一个边缘提取功能,就是形态学处理中的边缘提取,实现方式就是像PPT那样操作了,这里不详细说明了,是个很简单的按钮。 在作业之外的更新有比较多,增加了“马赛克”功能(在“滤镜”中),可以很方便地对整张图片进行打码操作,至于局部打码操作可能会在之后的更新中实现,请耐心等候。 然后还更换了鼠标图案,使大家更容易知道鼠标是否已经处于图片区域中,鼠标图案是片很漂亮的叶子哦(出自KEY的AIR)。 这次更新还增加了瞬时更新鼠标所指像素点的RGB颜色值,横纵坐标,还有当前鼠标所指的颜色的显示。 在之前的版本中,如果想要获得鼠标所指的像素点的像素信息,只能通过鼠标的右键点击,这样有时候会不太方便,而现在的话在主界面就已经可以瞬时看到了,而且清晰明了简单易用。 然后还修复了模糊与中值函数的内存泄漏BUG,相信之后还会有更多的BUG等着来修复- -。 然后本次最大的更新就是!!!本软件支持Jpg格式了!师姐可以试下用这个软件打嘅Jpg格式的图片哦!这个更新简直太人性化了,以后做图像处理终于不用每次都要打开PS把Jpg转换为Bmp再操作了! 最后,再次谢谢师姐能够读完这个文档,如果还有什么问题的话就联系我吧,联系方式就在软件中了欢迎点击- -,谢谢! ================================================================================================================================================================ 2014年4月8日更新说明: 这次更新的是DTF跟FFT这两个功能,DFT的函数在Bmp.cpp的DFT里面,在里面已经加上了注释了。 而FFTfft12_ifft12.cpp这个文件里面,文件包括了一维FFT和二维FFT的函数,当然IFFT也是存在的。 关于DFT的部分,由于我在DFT的算法里面使用了Complex类与COMPLEX结构体(类包含了运算符的重载与一些常用函数),毕竟C语言并没有专用的复数运算可以调用。 所以在进行DFT运算时会不断调用类的构造函数与析构函数,这导致了进行DFT运算使用的时间比Matlab写出来的要慢很多。 虽然处理起来时间比较长,但是相比FFT的优势是可以对非2的幂数的大小的图片进行傅里叶变换,而非2幂数的FFT的话如果进行补0再运算的话会导致算出的频域是错误的频域(毕竟随意加0会直接影响到这个图片)。 然后为了加快DFT的运算,这里我使用的方法是将复数因子(e的多少次方的那个)分成横向与纵向的乘积,先算出w_h0跟w_w0,之后的其他任何一个因子其实都是他们的幂相乘。 利用这个方法先把因子算好,这样就不用在每一步都计算sin跟cos的值,使到时间变快了很多。 虽然如此,计算一幅256*256的图使用I5CPU都得需要10秒钟,所以这种算法只能将着用,因此我也懒得写IDFT了- -。 所以为了偷懒我的IDFT直接调用了IFFT的函数,也就是说,对于不是2的幂数的图片,你可以进行DFT,但是不能变回来- -。 关于FFT的部分,其速度比Matlab的FFT算法快了不是一般的多,所以在之后的滤波处理中都使用了FFT。 但是FFT的缺点是在进行FFT运算前你必须要把图片大小转成2的幂数(我的软件自带缩放哦~),当然不一定要宽高相等,256*1024也是可以的。 关于这一点我在以后可能会使用因子法来进行FFT而不用现在的radio2法,这样的话可以对非2幂数的图片进行FFT了。 不过由于时间关系,现在的程序依然只能对2的幂数的大小进行FFT,这个非常抱歉。 关于幅值与角度的显示,在FFT滤波选项中可以查看到,当然查看角度的图片并没有什么意义就是了- -。 然后无论是8位图,24位图还是32位图,DFT跟DDT都完美支持! 然后就是那个FFT滤波的对话框,这里只写出了一个雏形(毕竟这几天清明节没有时间去写,求原谅- -),其实只能看看有什么功能而已,实现的函数其实还没有写的- -。 不过这次的作业并没有要求需要写FFT之后的滤波,所以也不算没有完成作业? 最后,无论是DFT还是FFT全都是自己一个代码一个代码写上去- -,研究傅里叶变换还把信号与系统的书都拿出来了,所以这次的作业真的好辛苦啊! 所以虽然功能尚未完善,但是请体谅!最后,再次谢谢师姐能够读完这个文档,如果还有什么问题的话就联系我吧,谢谢!(为了防止联系不到我,程序里面已经加入了“联系我”按钮了~) ================================================================================================================================================================ 2014年3月31日更新说明: 鉴于上个版本的软件功能还没有完整,而且内存存在泄漏的BUG会导致长时间使用时消耗内存过大。 所以这个版本将完善了很多功能,以及优化了内存问题,基本可以实现长时间使用了嗯嗯~撒花~~~~ 首先,程序的基本操作没有变化,基本都是右键操作,这次增加了重新读取图片功能,可以很方便地对图片进行还原处理。 然后,这次的作业是直方图与滤波器的操作,这些操作可以在直方图的选项与滤波器的选项中选择。 【色调均化】直方图的操作包括色调均化(我用了半天时间终于把PS的色调均化搞懂了,原来PS的色调均化根本不需要转什么色彩空间哦!),色调均化采用的是PS的算法。 【直方图调整】除了色调均化外还有查看直方图功能,该功能可以查看图片各个颜色的直方图以及总颜色(RGB)的直方图,还可以对直方图进行拉伸变换以提高图片的对比度,具体操作只需要用鼠标在直方图上使用左键或右键移动即可。 【曲线】除此之外,还可以对直方图进行函数变化(曲线),我尽量模拟出了PS的曲线效果,但是技术有限- -,这里的曲线只允许一种颜色的函数出现10个折点,合计一共可以出现40个折点,但是基本觉得是够用得了,使用方法依然是很简单,用鼠标点击曲线某一点即可添加或者取消折点。 【平均】这次的作业还有一个就是滤波器的操作,同样在右键菜单中可以选择平均滤波的使用,具体用法很简单,这里就不说明了。 【中值】中值滤波和平均滤波的操作差不多,也不一一说明了。 【锐化】个人觉得除了拉普拉斯的锐化方式之外的锐化都很难看所以就只保留了这两种锐化方式了,将就着用把- -。 【自定义滤波器】为了能够灵活地使用滤波器,一个自定义滤波器的功能还是需要的,具体的功能就跟PS的差不多吧,不过这个功能有个缺点就是当你需要输入负数时,你需要先输入数字再在前面输入负号- -,输入分数时小数点必须快速输入(我设置了一个不太快的刷新- -),虽然现在已经找到解决办法,但是懒得改了就将就着用吧- -。 在实现函数方面,还是在Bmp.cpp与其头文件中,为了让师姐能够容易地找到各个函数的所在,我已经在函数的定义前加上它的中文功能了,相信应该很容易找到。 至于函数过程的注释,由于老师没有要求在作业中需要写出来,同时函数太多也不好写,所以就算了- -,其实算法部分的话要读懂并不那么困难的。 为了方便,我在这里还是把这次作业所需要的函数列出来把(全部函数都在Bmp.cpp中): void Bmp::SmoothAverage(int x,int y)//平均滤波 void Bmp::SmoothMedian(int x,int y)//中值滤波 void Bmp::FilterDefine(double filter[5][5],int divide,int move)//自定义滤波器 void Bmp::CurveFunction(int color,int curve[256])//实现图片的函数变换,就是曲线啦 int** Bmp::GetHistogram()//计算图片的直方图,非归一化直方图 void Bmp::HistogramEqualization()//色调均化,算法采用PS的算法 void Bmp::Histogram(int Color,int Min,int Max)//直方图函数处理,就是直方图拉伸啦 以上就是这次作业的内容啦,为了方便,我还加入了 图像缩放 彩色转灰度 阈值 等功能,基本上这个程序已经越来越完善了,接下来就是慢慢添加功能以及修改细节啦。 最后,再次谢谢师姐能够读完这个文档,如果还有什么问题的话就联系我吧,谢谢! ================================================================================================================================================================ 因为本人的兴趣,我把这次作业做的比较复杂,功能比较多,所以您在找打开保存等函数的时候可能会有点麻烦,所以在这里放一个说明文档。 首先,本程序生成的exe应用程序在Realse目录下,那个有个很漂亮的宝石图标的程序就是了! 打开程序后,您可以使用打开按钮来打开图片,也可以直接把图片用鼠标直接拖入程序中打开(个人推荐第二种,毕竟方便)。 这个程序是支持8位图24位图32位图的哦! 打开后可以使用保存按钮保存图片,使用存为文本来保存为文本文档,我的程序会把图片以颜色层数为依据来保存文本文档。 如果您想使用我加入的其他功能,可以对着图片按右键,在弹出的菜单中选中你想要的功能就是了,但是锐化功能还没写(因为时间不够- -),其他的功能可以将就用下。 如果您想要找到这个程序的cpp文件,他在这个文件夹的同名文件夹下(MFC读取显示与保存BMP图像\MFC读取显示与保存BMP图像\Bmp.cpp(.h))。 因为我把与图像有关的操作封装成一个类(C++的),用以与其他的窗口类分来容易编写。 而与本次作业有关的函数有Open(),Save(),SaveAsTxt(),三个,在Bmp.cpp中都可以找到,并且都已经注释了,您可以去那里查找观看。 当然除了这些之外,我还写了其他一些函数如反色反转滤波器等,但是没有注释(因为太懒),师姐(兄)有兴趣的话也可以去看看,当然也可以直接去程序那里看效果。 最后如果师姐(兄)有什么看不懂或者找不到函数在哪的话可以联系我帮你找(毕竟我也觉得文件写得有点长而且复杂),我的手机号码是15800037916(666542)。 谢谢师姐(兄)看完!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值