摘要:一直以來都是用MATLAB做信號處理,得到預處理的特徵後再用Python進一步應用神經網絡之類的方法。這裏利用Python實現了常規波束形成(CBF)、MVDR波束形成以及波束掃描方位估計。本文實現的都是窄帶波束形成,一種簡單的寬帶波束形成方法時直接將不同頻率的窄帶輸出相加,比較容易擴展。
系列目錄
- Python信號處理:快速傅里葉變換(FFT),短時傅里葉變換(STFT),窗函數,以及濾波
- Python信號處理:自相關函數(對標MATLAB中的autocorr)
- Python信號處理:波束形成及目標方位估計,CBF、MVDR
- Python信號處理:cvxpy工具包求解稀疏約束優化問題
目錄
- 波束形成和方位估計
- 常規波束形成器
- MVDR波束形成器
1. 波束形成和方位估計
波束形成就是通過一個加權向量對陣列接收到的數據加權相加得到輸出,所以確定是波束形成中非常重要的步驟,知道就知道了波束輸出。波束形成器的設計問題其實就是設計加權向量。
已知陣列的方向響應向量爲
,則波束響應爲
波束響應只與加權向量和方向響應向量有關,而與是否存在信號無關。對於確定方向的加權向量,改變方向響應向量就可以得到波束響應能量相對於不同方向的函數圖,也就是波束圖。波束圖反映了波束形成器的空域濾波性能。
加權向量是波束觀察方向的函數,因此在波束觀察方向的波束輸出功率可以表示爲
其中是接收數據的協方差矩陣。改變波束觀察方向,就可以獲得在整個觀察區內的波束輸出功率,此時的到的就是波束掃描方位譜。
波束形成和方位估計,分別可以得到波束圖和波束掃描方位譜,兩者十分相似,在常規波束形成中,兩者甚至在形式上一樣的。但兩者的意義差別很大。
-
波束圖用於表示波束形成器的空域濾波性能,即給定波束形成器,看它對各方位信號的響應大小。計算波束圖時,波束加權向量是固定的,通過掃描陣列的方向響應向量獲得不同方向的波束響應。從波束圖可以觀察到各方位信號對感興趣的方位(波束觀察方向)波束輸出的影響。波束圖與是否存在信號無關。
-
方位譜用於估計各方位到達信號功率的大小。利用波束掃描法計算方位譜時,加權向量是觀察方向的函數,方位譜的峯值所在可以用於估計信號的方位。
2. 常規波束形成器
常規波束形成器的原理是延時加權求和,加權向量正好就是陣列方向相應向量相位的變化,即,因此波束響應爲
其中,波束形成時的是不變的。
用於方位估計時,信號方位時固定的,波束觀察方向在觀察區中變化,得到方位譜爲
python實現如下,代碼中變量命名和公式中相同,波束圖和波束掃描方位譜如圖所示。在白噪聲背景下,CBF的兩幅圖是相同的。
import matplotlib.pyplot as plt
import numpy as np
d = np.array([15]) * np.ones([18, 1])
f = 50
lmbda = 1500 / f
N = 18
n = np.arange(0, N, 1).reshape([-1, 1])
# 波束形成
theta = np.arange(-90, 90, 0.1).reshape([1, -1])
theta_0 = 10
theta = theta / 180 * np.pi
theta_0 = theta_0 / 180 * np.pi
a = np.exp(1j * 2 * np.pi * np.sin(theta) * n * d / lmbda)
w = np.exp(1j * 2 * np.pi * np.sin(theta_0) * n * d / lmbda)
B = np.dot(a.transpose(), np.conj(w))
B = np.abs(B) / np.max(np.abs(B))
fig, ax = plt.subplots(1, 2, sharex=True, sharey=True, figsize=(7, 2), dpi=300)
plt.subplots_adjust(wspace=0.1, hspace=0.05)
ax[0].plot(theta.reshape([-1,1])*180/np.pi, 20 * np.log10(B.reshape([-1,1])), '--')
# 方位估計
theta_0 = np.arange(-90, 90, 0.1).reshape([1, -1])
theta = 10
theta = theta / 180 * np.pi
theta_0 = theta_0 / 180 * np.pi
a = np.mat(np.exp(1j * 2 * np.pi * np.sin(theta) * n * d / lmbda))
w = np.mat(np.exp(1j * 2 * np.pi * np.sin(theta_0) * n * d / lmbda))
sigma = np.transpose(np.conj(w)) * a * np.transpose(np.conj(a)) * w
sigma = np.diag(sigma)
sigma = sigma / np.max(sigma)
ax[1].plot(theta_0.reshape([-1,1])*180/np.pi, 10 * np.log10(sigma))
3. MVDR波束形成器
最小方差無失真響應波束形成器,顧名思義有兩個成分:1. 最小方差,2. 無失真,因此可以表示爲
其中,優化目標表明爲最小輸出噪聲方差,約束爲無失真約束。實際中通常使用接收信號的協方差矩陣代替噪聲協方差矩陣,此時方法也可以叫做最小功率無失真響應波束形成器(MPDR),但叫MVDR也可以。
通過求解該約束優化問題,就可以得到波束形成器的加權向量,進而得到波束輸出響應。有兩種思路求解該優化問題:
- 常規解法,通過lagrange乘子法,手動求解計算加權向量以及波束相應輸出。
- 工具包求解,利用一些優化工具包,直接求解該優化問題得到答案——這種方法實際上解決了大部分的波束形成問題,因爲很多波束形成問題都是一個目標函數,再加上很多的約束,如果工具包能求解對應的優化問題,波束輸出就有了。
這裏直接給出第一種方法求出來的加權向量。第二種解法在後面內容中會詳細介紹,作爲一種通用的波束形成方法。因爲都是優化問題,波束形成在算法上幾乎沒有什麼難題,只有工程中的問題。
scipy.optimize.minimize失敗了,因爲只能處理實數的問題。MATLAB的CVX工具箱是能處理複數的,不知道cvxpy怎麼樣。。
cvxpy好像解不出複數最優解,目標函數裏可以有複數操作,但最終的值必須是實數的,而且解出來的最優解也一直是實數,肯定哪裏不太對。。
波束響應和波束輸出功率,和第一部分的公式相同,即
python實現如下,代碼中變量命名和公式中相同,波束圖和波束掃描方位譜如圖所示。從波束圖可以看出,當信號方向(-10°)和波束觀察方向(10°)不同時,MVDR在信號方向會形成一個凹陷,降低了信號(此時信號算是干擾)的能量。
import matplotlib.pyplot as plt
import numpy as np
d = np.array([15]) * np.ones([18, 1])
f = 50
lmbda = 1500 / f
N = 18
n = np.arange(0, N, 1).reshape([-1, 1])
# 信號
theta = -10
theta = theta / 180 * np.pi
x = np.exp(1j * 2 * np.pi * np.sin(theta) * n * d / lmbda)
Rx = np.mat(1000 * np.dot(x, np.transpose(np.conj(x))) + 1 * np.eye(N))
# # 波束形成
theta = np.arange(-90, 90, 0.1).reshape([1, -1])
theta_0 = 10
theta = theta / 180 * np.pi
theta_0 = theta_0 / 180 * np.pi
a = np.mat(np.exp(1j * 2 * np.pi * np.sin(theta) * n * d / lmbda))
a_theta_0 = np.mat(np.exp(1j * 2 * np.pi * np.sin(theta_0) * n * d / lmbda))
w = Rx.I * a_theta_0 / (a_theta_0.H * Rx.I * a_theta_0)
B = w.H * a
B = np.abs(B) / np.max(np.abs(B))
fig, ax = plt.subplots(1, 2, sharex=True, sharey=False, figsize=(7, 2), dpi=300)
plt.subplots_adjust(wspace=0.35, hspace=0.05)
ax[0].plot(theta.reshape([-1,1])*180/np.pi, 20 * np.log10(B.reshape([-1,1])), '--')
# 方位估計
theta_0 = np.arange(-90, 90, 0.1).reshape([1, -1])
theta_0 = theta_0 / 180 * np.pi
sigma = []
for i in range(theta_0.shape[1]):
a_theta_0 = np.mat(np.exp(1j * 2 * np.pi * np.sin(theta_0[:, i]) * n * d / lmbda))
sigma.append((1 / (a_theta_0.H * Rx.I * a_theta_0)))
sigma = np.array(sigma).reshape([-1, 1])
ax[1].plot(theta_0.reshape([-1,1])*180/np.pi, 10 * np.log10(sigma))