Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (898.2 KB, 120 trang )
ln 4096 = y ln2
ln 4096
= y = 12
ln 2
hàm ln gọi là loga tự nhiên (loga cơ số e):
log 2 x =
ln x
ln 2
tơng tự
log B x =
ln x log 10 x
=
ln B log 10 B
(5.9)
Trên cở sở của công thức 5.9 trên dòng lệnh thứ hai ta tính số lợng bit cần
thiết để lấy mẫu, số bít đợc làm tròn xuống bằng số nguyên gần nhất. Công thức
5.9 đợc đa vào chơng trình nh sau:
public satatic int log2(int n) {
return(int) (Math.log(n)/Math.log(2.0));
}f
dòng 5 thực hiện phép nhân vói 2:
N=1<
Lớp futils. Timer : làm chuẩn DFT
Benchmarking là một thủ thuật cho phép thực hiện phần mềm và phần cứng
nối kết với nhau. Một trong những điểm đặc biệt của Java là biên dịch một lần
nhng chạy mọi chỗ, đặc tính này cho phép các byte code đợc thực hiện trên các
"nền" khác nhau của machineJava. Thêm nữa chúng ta có máy ảo Java cho phép
thực hiện trên các phần cứng khác nhau. Sự sắp xếp này cho phép so sánh các
nền phần cứng khác nhau khi ta thi hành các byte code. Benchmarking có thể
đánh giá đợc hiệu quả của thuật toán trên cơ sở lấy thời gian làm đại lợng đo.
Một đặc tính khác nữa đáng quan tâm là thời gian thực hiện đối với hai thuật
toán trên cùng một dữ liệu thì khác nhau .
Nội dung của lớp
package futils.bench;
import java.i0.*;
public class Timer{
public Timer()
pblic void mark()
public void record()
public float elapsed()
public void report(PrintStream pstream)
public void report()
}
Cách sử dụng
Giả sử ta khai báo các biến nh sau:
Timer t1;
Khai báo một biến mới thuộc kiểu lớp Timer (ban đầu đợc thiết lập giá trị 0):
t1=new Timer();
Để đánh dấu một thời điểm tơng tự nh việc bấm đồng hồ bấm giờ.
t1.mark();
64
Để biết đợc tổng số thời gian đã trôi qua với thời điểm đã lấy làm mốc kể từ khi
phơng thức record đợc gọi lần cuối cùng.
t1.record();
Trả về thời gian đợc tính bằng giây:
t1.elapsed();
Hiển thị thời gian trôi qua, đa tới PrintScreen thông qua biến pstream:
t1.report(pstream);
Hiển thị thời gian , đa tới System.out:
t1.report();
Benchmarking
Trong phần này thực hiện tính thời gian thực hiện biến đổi DFT bằng lớp
Timer. Trong ví dụ chúng ta sử dụng một cách tính DFT và chuẩn tắc một phơng
thức. Benchmarking thực hiện biến đổi DFT với số điểm lấy mẫu là 2048 điểm
sẽ cần 55 giây nếu chạy trên máy PPC 601 100MHz và không có bộ biên dịch
JIT. Chúng ta đa ra cách tính DFT trong gói lyon.audio nằm trong lớp
AudioFrame.
public void dft(){
FFT f = new FFT();
double [] doubleData = ulc.getDoubleArray();
double [] psd;
//Time the fft
Timer t1 = new Timer();
t1.mark();
psd=f.dft(doubleData);
//Stop the timer and report.
t1.record();
System.out.println("Time to perform DFT");
t1.record();
f.grapgs();
new DoubleGraph(psd,"psd");
}
IDFT
Biến đổi IDFT là thực hiện quá trình biến đổi ngợc lại với biến đổi DFT, tín
hiệu V(f) trong miền tần số đợc chuyển sang miền thời gian theo biểu thức:
v(t ) = F
1
+
[V ( f )] = V ( f )e 2ift dt
Để giải quyết vấn đề lấy mẫu tín hiệu (theo công thức 5.1). Chúng ta phải
thực hiện biến đổi IDFT đợc tính theo công thức sau:
N
1
V k = 2 / N
e ijk
(5.10)
j=
0
Trong chơng 4 chúng ta đã biết theo công thức Euler thì : e i = cos +sin
Thay thế công thức 5.4a vào trong công thức 5.10 nhận đợc kết quả:
65
N 1
[
]
v k = cos( 2 jk / N ) + i sin( 2 jk / N ) V j
j= 0
(5.11)
Chú ý rằng phép nhân hai số phức đợc tính nh sau:
z1z2=(x1+iy1)(x2+iy2)=x1x2-y1y2+i(x1y2+y1x2)
(5.11a)
Trên cơ sở biểu thức 5.11a ta chia kết quả thành hai phần thực và ảo.
(5.11b)
(5.11c)
Real(z 1z2)=x1x2-y1y2
Imaginary(z 1z2)=x1y2+x2y1
Do chỉ quan tâm đến việc xử lý phần thực của tín hiệu do vậy không cần thiết
tính toán kết quả phần ảo trong 5.11c. Thay thế 5.11b vào trong 5.11:
N 1
[
]
real (v k ) = cos( 2jk / N ) real (V j ) sin ( 2jk / N ) imaginary (V j ) (5.12)
j =0
Tính toán chỉ với phần thực của IDFT cần thực hiện 2N phép nhân và N phép
cộng cho mỗi một chỉ số k. Hãy so sánh 5.12 và 5.5,
Vk =
1
N
N 1
( cos( 2jk / N ) i sin ( 2jk / N ) ) v
j =0
j
Đoạn chơng trình sau đây thực hiện biến đổi IDFT theo công thức 5.12
//assume that r_data and i_data are
// set. Also assume that the real
// value is to be returned
public double[] idft() {
int N= r_data.length;
double twoPiOnN = 2*Math.PI/N;
double twoPijkOn;
double v[] = new double[N];
System.out.println("Executing IDFT on "+N+" points...");
for(int k=0;k
twoPikOnN = twoPiOn*k;
for(int j =0;j
twoPijkOn = twoPikOn*j;
v[k]+ =r_data[j]*Math.cos(twoPIjkON)i_data[j]*Math.sin(twoPijkOn);
}
}
return(v);
}
Tạo dữ liệu mẫu kiểm tra DFT và IDFT
Một numeric check là phần tổ hợp của mọi lớp. Lớp FFT có một phơng thức
gọi là testDFT nhiêm vụ của lớp này là kiểm tra tính đúng đắn của phép biến đổi
DFT và IDFT. Với số lợng mẫu là 8, phơng thức sẽ kiểm tra các dữ liệu đa vào
và ra có tuân theo công thức tính hay không.
public static void testDFT() {
int N = 8;
FFT f = new FFT(N);
double v[];
double x1[] = new double[N];
for (int j=0; j
x1[j] = j;
66
// take dft
f.dft(x1);
v = f.idft();
System.out.println("j\tx1[j]\tre[j]\tim[j]\t v[j]");
for (int j=0; j < N; j++)
System.out.println(
j+"\t"+
x1[j]+"\t"+
f.r_data[j]+"\t"+
f.i_data[j]+"\t"+
v[j]);
}
5.2 FFT
FFT là một họ các thuật toán cho phép tăng tốc độ tính toán DFT:
Vk =
1
N
N 1
e
2ij / N
j =0
vj
Tính toán DFT một cách trực tiếp theo công thức cần 0(N 2) phép nhân trong
khi FFT chỉ cần 0(NlogN) phép nhân. Trong phần này chúng ta đa ra thuật toán
FFT để rút ngắn thời gian thực hiện, thuật toán FFT cơ số 2 còn gọi là thuật toán
Cooley-Tukey. Thuật toán Cooley-Tukey là một trong số các thuật toán FFT đợc
sử dụng rộng rãi nhất. Ta gọi thuật toán có số 2 bởi vì tín hiệu đợc lấy mẫu với
số lợng mẫu là lũy thừa của 2. Thuật toán FFT chia tín hiệu lấy mẫu thành hai
phần, một là tập hợp gồm các mẫu có chỉ số lẻ, hai là tập hợp các mẫu có chỉ số
chẵn. Cách làm này là theo bổ đề của Danielson-Lanczos.
Vk =
[
1
V e k +W k V 0 k
N
]
[0.. N 1]
k
Dới đây là công thức gốc của bổ đề.
Với W=e-2i/N tacó
W ik = W j .W
j ( k 1)
(5.13)
Kết hợp 5.13 cho ta kết quả:
1
Vk =
N
N 1
W
jk
Vj
(5.14)
j= 0
Phân chia vế trái của 5.14 dới dạng hai phần chỉ số chẵn và chỉ số lẻ
N / 2 1
1 N / 2 1 2 jk
Vk = W V2 j + W ( 2 j + 1) k V2 j + 1
N j= 0
j= 0
(5.15)
Công thức 5.15 là tổng của hai thành phần chẵn lẻ, ví dụ nếu j=0,1,2,3... thì
2j=0,2,4,6,8...2j+1=1,3,5,7...
Ta viết lại biểu thức 5.15 dới dạng:
N / 2 1
1 N / 2 1 2 jk
Vk = W V2 j + W 2 jk W kV2 j +1
N j=0
j=0
Do Wk không phụ thuộc vào chỉ số nên ta đa nó ra ngoài tổng.
67
N / 21
1 N / 2 1 2 jk
k
Vk = W V2 j + W W 2 jkV2 j +1
N j=0
j=0
(5.16)
Cuối cùng ta có biểu thức đợc viết gọn lại.
1
N
Vk =
V e + W k V 0
k
k
k [ 0.. N 1]
(5.17)
Từ công thức 5.17 ta nhận thấy có thể chia các mẫu tín hiệu thành các phần
có chỉ số chẵn lẻ. Bổ đề Danielson-Lanczos cho thuật toán chia tổng số mẫu
thành hai, tiếp tục hai nửa này lại đợc chia thành hai tơng ứng với chỉ số chẵn và
lẻ. Quá trình này kết thúc khi mỗi nửa chỉ còn 2 mẫu. Với cách làm này ta giảm
đợc số lợng phép tính từ 0(N2) xuống còn 0(N logN) .
Ví dụ với số lợng mẫu lấy là 8 (N=8) thuật toán chia theo bổ đề có dạng:
Hình 5.1
Cách thông thờng cho phép thực hiện thuật toán theo bổ đề là dùng lời gọi đệ
qui. Tuy nhiên chúng ta đã biết thời gian thực hiện một lời gọi đệ qui dài gấp
khoảng 6 lần một lời gọi thông thờng. Để tránh đợc nhợc điểm này chúng ta sử
dụng thuật toán đảo bít để giảm thời gian thực hiện phép biến đổi. Trên hình 5.2
là thuật toán đảo bít.
Chúng ta có thể thực hiện thuật toán đảo bít bằng chơng trình nh sau:
int bitr(int j){
int ans = 0;
for(int i=0; i< n; i++) {
ans = (ans <<1)+(j&1);
}
return ans;
}
68
Hình 5.2
Phơng thức bitr thực hiện với hai thanh ghi dịch (viết bằng chơng trình) có dạng
sau:
jn jn-1 ...
j1 j0
an an-1 .... a1 a0
Sau khi thực hiện thuật toán ta có kết quả đơn giản hơn nhờ vào tính chất
tuần hoàn của Vk : Vk+N = Vk.
Lớp FFT
Lớp FFT là một lớp có thuộc tính public nằm trong gói lyon.audio . Lớp dựa
vào các gói grapher.Graph để thực hiện các thủ tục về đồ hoạ. Gói grapher
cung cấp một giao diện đơn giản tự động thực hiện thao tác vẽ lên các đồ hình
cần thiết theo một độ chính xác xác định. Bình thờng chỉ có một phơng thức đợc
thực hiện. Graph.graph có thể đợc gọi một cách trực tiếp bởi nó là một phơng
thức tĩnh, và các phép tính toán đợc thực hiện với các mảng dữ liệu kiểu số thực
double.
Nội dung của lớp
package lyon.audio;
import java.io.*;
import java.awt.*;
import grapher.Graph;
import futils.bench.Timer;
public class FFT extends Frame {
public FFT(int N)
public FFT()
public void graphs()
public void graphs(String t)
public static double getMaxValue(double in[])
public static int log2(int n)
public static double[] arrayCopy(double [] in)
public double [] computePDS()
public double []dft(double v[])
public double [] idft()
public double [] getReal()
public double [] getImaginary()
public void forwardFFT(double in_r, double in_i[])
69
public
public
public
public
public
public
public
}
void reverseFFT(double in_r[], double in_i[])
void printArray(double[] v, String title)
void printArray(String title)
static void main(String args[])
static void timeFFT()
static void testFFT()
static void testDFT()
Cách sử dụng
Lớp FFT sử dụng các mảng dữ liệu kiểu số thực double để tính toán. Những
mảng dữ liệu này là phân biệt tách riêng nhau và đợc sử dụng trợ giúp cho các
tính toán khác trong chơng trình. Thêm nữa thuật toán Coley-tukey không giữ lại
các kết quả trung gian của lần tính trớc mà nó chỉ giữ lại kết quả của lần tính
hiện tại. Trong tất cả các tính toán của mình lớp FFT đều sử dụng số thực kiểu
double, và lớp FFT đợc sử dụng cho các mảng một chiều chứa dữ liệu âm thanh
audio.
Giả sử ta khai báo các biến nh sau:
FFT f;
int N=8;
double inputArray[];
String title = "my data title";
double in_r[];
double in_i[];
Tạo ra biến mới thuộc kiểu lớp FFT có hai mảng số thực double nằm bên
trong, mỗi mảng có độ dài N:
f = new FFT(N);
Tạo một biến mới thuộc kiểu FFT nhng không có mảng dữ liệu:
f =new FFT();
Để vẽ đồ hình của các mảng dữ liệu thực và ảo của tín hiệu:
f.graphs();
Vẽ nhng có hiển thị một tiêu đề:
f.setTitle(title);
Nhận đợc giá trị lớn nhất có trong mảng dữ liệu tín hiệu đa vào xử lí:
FFT.getMaxValue(inputArray);
Tính log của số nguyên
Int numberOfBits = FFT.log2(N);
Sao chép lại một mảng số thực kiểu double:
adouble = FFT.arrayCopy(inputArray);
Tính mật độ phổ năng lợng PDS của DFT hay FFT:
aDoubleArray = f.computePSD();
Nhận lại phần thực , phần ảo trong kết quả tính lần cuối cùng:
aDoubleArray = f.getReal();
aDoubleArray = f.getImaginary()
Lu lại IDFT của dữ liệu đã tồn tại và trả lại phần thực của kết quả:
70
aDoubleArray = f.idft();
Để lu lại FFT lần đầu trên hai mảng dữ liệu đa vào.
f.forwardFFT(in_r, in_i);
Lu kết quả IFFT vào hai mảng :
f.reverseFFT(in_r, in_i);
Hiển thị mảng dữ liệu kèm theo một dòng tiêu đề:
f.printArray(aDoubleArray, title);
Hiển thị phần thực cùng với một tiêu đề:
f.printReal(title);
Kiểm tra tính đúng đắn của các phong thức tính DFT,IDFT, FFT, IFFT:
FFT.main();
Tính thời gian cần thiết để thực hiện biến đổi FFT:
FFT.timeFFT();
Kiểm tra FFT và IFFT
Lớp FFT có một phơng thức tĩnh (static method) cho phép ta kiểm tra tính
đúng đắn của các phép biến đổi DFT, IDFT, FFT và IFFT. Phơng thức thực hiện
việc kiểm tra với 2048 mẫu tín hiệu. Để thực hiện việc kiểm tra này ta phải gọi
tới phơng thức main nh sau:
FFT.main();
Đoạn lệnh thực hiện phơng thức main có dạng:
public static void main(String args[]) {
testDFT();
testFFt();
testIFFT();
}
Kiểm tra với số lợng mẫu N=8 là một đờng nghiêng tuyến tính, chia thành
các đoạn đều nhau tăng dần tuần tự. Thời gian thực hiện biến đổi Fourier cho
2048 mẫu đợc lu trong hai mảng số thực kiểu double mỗi mảng có 2048 phần tử,
một mảng dành cho phần thực mảng còn lại dành cho phần ảo. Khi thực hiện
biến đổi với số mẫu N=8 trên màn hình ta nhận đợc kết quả sau:
Executing DFT on 8 points...
Executing IDFT on 8 points...
j
x1[j] re[j] im[j]
0
0.0
3.5
1
1.0
-0.5000000000000004
1.2071067811865475
1.0000000000000016
2
2.0
-0.5000000000000002
0.49999999999999983
2.000000000000006
3
3.0
-0.5
0.20710678118654813
4
4.0
-0.5
-4.2861222383783204E-16 3.999999999999997
5
5.0
-0.5000000000000011
-0.2071067811865488
5.000000000000001
6
6.0
-0.5000000000000019
-0.5000000000000007
6.000000000000001
0.0
v[j]
-9.992007221626409E-16
2.9999999999999973
71