昨晚,有同學私信諮詢:如何將矩形數據轉爲扇掃圖像?面對這個問題,我也是一臉懵逼,什麼是扇掃?矩形數據又是啥?細問之下,才知道這是B型超聲設備的數據處理問題。B超輸出的數據保存在一個二維數組中,但顯示在屏幕上的卻需要轉換爲扇形。如下圖所示:
稍微思考一下,應該不難解決。比較直接的方法是,將二維數組的每一列旋轉合適的角度,就可以拼成一副圖像。我們用參數angle表示扇形夾角的一半,用參數k表示輸出圖像的高度與每一列數據數量的比值,很容易寫出如下代碼:
# -*- coding:utf-8 -*-
import numpy as np
from PIL import Image
def square2fan(fn_squ, fn_fan, angle=45, k=1):
"""將矩形圖像轉爲扇形
fn_squ - 輸入文件名
fn_fan - 輸出文件名
angle - 扇形夾角度數
k - 扇形因子,k大於1輸出環形
"""
im = Image.open(fn_squ) # 打開輸入圖像爲PIL對象
mode = im.mode # 輸入圖像模式
w, h = im.size # 輸入圖像分辨率
rows, cols = int(np.ceil(h*k)), int(np.ceil(2*h*k*np.sin(np.radians(angle)))) # 輸出圖像高度和寬度
cols += cols%2 # 寬度爲單數則加1
im_squ = np.array(im) # 輸入圖像轉爲numpy數組
im_fan = np.zeros((rows, cols, im_squ.shape[2]), dtype=np.uint8) # 生成輸出圖像的numpy數組(全透明)
alpha = np.radians(np.linspace(-angle, angle, w)) # 生成扇形角度序列,長度與輸入圖像寬度一致
for i in range(w): # 遍歷輸入圖像的每一列
# 當前列各像素在輸出圖像上的行號
d = np.cos(alpha[i])*rows
lats = np.int_(np.linspace(d*(k-1)/k, d, h)).astype(np.int)
# 當前列各像素在輸出圖像上的列號
d = np.sin(alpha[i])*rows
lons = np.int_(np.linspace(cols/2+d*(k-1)/k, cols/2+d, h)).astype(np.int)
# 輸出圖像上對應的點替換爲輸入圖像的點
im_fan[(lats, lons)] = im_squ[:,i]
# 保存爲文件
im = Image.fromarray(im_fan, mode=im.mode)
im.save(fn_fan)
if __name__ == '__main__':
square2fan('demo.png', 'out.png', angle=45, k=1.0)
然而,輸出圖像的效果卻不夠完美:圖像下部出現了鏤空的白點。
沒關係,我們再加上一個臨近點插值,並用matplotlib將輸入數據和輸出數據畫在一起。完整代碼如下:
# -*- coding:utf-8 -*-
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
def square2fan(fn_squ, fn_fan, angle=45, k=1):
"""將矩形圖像轉爲扇形
fn_squ - 輸入文件名
fn_fan - 輸出文件名
angle - 扇形夾角度數
k - 扇形因子,k大於1輸出環形
"""
im = Image.open(fn_squ) # 打開輸入圖像爲PIL對象
mode = im.mode # 輸入圖像模式
w, h = im.size # 輸入圖像分辨率
rows, cols = int(np.ceil(h*k)), int(np.ceil(2*h*k*np.sin(np.radians(angle)))) # 輸出圖像高度和寬度
cols += cols%2 # 寬度爲單數則加1
im_squ = np.array(im) # 輸入圖像轉爲numpy數組
im_fan = np.zeros((rows, cols, im_squ.shape[2]), dtype=np.uint8) # 生成輸出圖像的numpy數組(全透明)
alpha = np.radians(np.linspace(-angle, angle, w)) # 生成扇形角度序列,長度與輸入圖像寬度一致
for i in range(w): # 遍歷輸入圖像的每一列
# 當前列各像素在輸出圖像上的行號
d = np.cos(alpha[i])*rows
lats = np.int_(np.linspace(d*(k-1)/k, d, h)).astype(np.int)
# 當前列各像素在輸出圖像上的列號
d = np.sin(alpha[i])*rows
lons = np.int_(np.linspace(cols/2+d*(k-1)/k, cols/2+d, h)).astype(np.int)
# 輸出圖像上對應的點替換爲輸入圖像的點
im_fan[(lats, lons)] = im_squ[:,i]
# 空白區域臨近點插值
for row in range(int(rows*(k-1)/k)+1, rows):
ps, pe = 0, 0
for col in range(cols):
if im_fan[row, col, 3] > 0:
if ps == 0:
ps, pe = col, col
else:
pe = col
for col in range(ps-1 ,pe):
if im_fan[row, col, 3] == 0:
im_fan[row, col] = im_fan[row, col-1]
# 繪圖
plt.figure('B超扇掃', facecolor='#f4f4f4', figsize=(15, 7))
plt.subplot(121)
plt.imshow(im_squ)
plt.subplot(122)
plt.imshow(im_fan)
plt.savefig('out_plt.png')
plt.show()
# 保存爲文件
im = Image.fromarray(im_fan, mode=im.mode)
im.save(fn_fan)
if __name__ == '__main__':
square2fan('demo.png', 'out.png', angle=35, k=1.2)
最終效果如下: