单通道雷达图像转数值校准失败,请求技术协助
解决单通道雷达图转dBZ数值的维度错误问题
错误原因
你的雷达图像是单通道调色板图像(PIL的P模式),读取后得到的是2D数组(形状为(高度, 宽度)),但原代码是针对3通道RGB图像编写的,默认图像是(高度, 宽度, 3)的3D数组。这会导致以下问题:
- 访问
img[i,j]得到的是单个索引值,而非RGB三通道数组,无法在_get_distances中进行三通道距离计算 np.sum(pixel)逻辑失效(单个数值的sum就是自身,无法判断是否为暗像素)
修复方案
最直接的方法是将单通道调色板图像转换为3通道RGB图像,让原代码的逻辑可以正常运行。只需修改图像读取的代码即可,其余逻辑无需大幅调整。
修改后的完整代码
import numpy as np import PIL.Image import matplotlib.pyplot as plt import matplotlib.cm # numba is an optional import, just here to make the function run faster import numba # Separated this function out since its the majority of the run time and slow @numba.njit() def _get_distances(pixel: np.ndarray, calibration_inputs: np.ndarray): # Create the outarray where each position is the distance from the pixel at the matching index outarray = np.empty(shape=(calibration_inputs.shape[0]), dtype=np.int32) for i in range(calibration_inputs.shape[0]): # Calculate the vector difference # NOTE: These must be signed integers to avoid issues with uinteger wrapping (see "nuclear gandhi") diff = calibration_inputs[i] - pixel outarray[i] = diff[0] ** 2 + diff[1] ** 2 + diff[2] ** 2 return outarray def _main(): # How many ticks are on the axes in the legend calibration_point_count = 17 fname = 'C:/Users/lucas-fagundes/Downloads/getImagem (1).png' fname_chart = 'C:/Users/lucas-fagundes/Downloads/legenda_ciram.png' # Whether to collect the calibration data or not setup_mode = False # --- 关键修改:将单通道调色板图像转换为RGB三通道 --- # 读取图像并转换为RGB模式,确保得到3D数组(H, W, 3) img = np.array(PIL.Image.open(fname).convert('RGB')) # 图例图像如果也是单通道,同样需要转换,这里假设图例是RGB img_chart = np.array(PIL.Image.open(fname_chart).convert('RGB')) if setup_mode: fig = plt.figure() plt.title('Select center of colourbar then each tick on legend') plt.imshow(img_chart) selections = plt.ginput(calibration_point_count + 1) # Use the first click to find the horizontal line to read calibration_x = int(selections[0][1]) calibration_ys = np.array([int(y) for y, x in selections[1:]], dtype=int) plt.close(fig) # Request the tick mark values calibration_values = np.empty(shape=(calibration_point_count,), dtype=float) for i in range(calibration_point_count): calibration_values[i] = float(input(f'Enter calibration point value {i:2}: ')) # Create a plot to verify that the bars were effectively captured for index, colour in enumerate(['red', 'green', 'blue']): plt.plot(img_chart[calibration_x, calibration_ys[0]:calibration_ys[-1], index], color=colour) plt.title('Colour components in legend') plt.show() else: # If you have already run the calibration once, you can put that data here # This saves you alot of clicking in future runs calibration_x = 6 calibration_ys = np.array([ 14, 43, 69, 93, 120, 152, 179, 206, 233, 259, 285, 312, 342, 371, 397, 421, 451]) calibration_values = np.array([ 78. , 73. , 68. , 63. , 58. , 53. , 48. , 43. , 38. , 33. , 28. , 23. , 18. , 13. , 10. , -10. , -31.5]) # Record the pixel values to match the colours against calibration_inputs = img_chart[calibration_x, calibration_ys[0]:calibration_ys[-1], :3].astype(np.int32) # Print this information to console so that you can copy it into the code above and not rerun setup_mode print(f'{calibration_x = }') print(f'{calibration_ys = }') print(f'{calibration_values = }') # print(f'{calibration_inputs = }') # Make the output array the same size, but without RGB vector, just a magnitude arrout = np.zeros(shape=img.shape[:-1], dtype=np.float32) # 改为float32存储dBZ数值,避免整数截断 # Iterate through every pixel (can be optimized alot if you need to run this frequently) for i in range(img.shape[0]): # This takes a while to run, so print some status throughout print(f'\r{i / img.shape[0] * 100:.2f}%', end='') for j in range(img.shape[1]): # Change the type so that the subtraction in the _get_distances function works appropriately pixel = img[i, j].astype(np.int32) # If this pixel is too dark, leave it as 0 if np.sum(pixel) < 100: continue # idx contains the index of the closet match idx = np.argmin(_get_distances(pixel, calibration_inputs)) # Interpolate the value against the chart and save it to the output array arrout[i, j] = np.interp(idx + calibration_ys[0], calibration_ys, calibration_values) # Create a custom cmap based on jet which looks the most like the input image # This step isn't necessary, but helps us compare the input to the output cmap = matplotlib.colormaps['jet'] cmap.set_under('k') # If the value is below the bottom clip, set it to black fig, ax = plt.subplots(3, 1, gridspec_kw={'wspace': 0.01, 'hspace': 0.01}, height_ratios=(3, 3, 1)) ax[0].imshow(arrout, cmap=cmap, vmin=-31.5, vmax=78); ax[0].axis('off') # 设置vmin/vmax匹配校准范围 ax[1].imshow(img); ax[1].axis('off'); ax[1].sharex(ax[0]); ax[1].sharey(ax[0]) ax[2].imshow(img_chart); ax[2].axis('off') plt.show() if __name__ == '__main__': _main()
额外优化说明
- 将
arrout的 dtype 改为np.float32:原代码用img.dtype(通常是uint8)会截断dBZ的浮点数值,导致精度丢失 - 手动设置
vmin和vmax:让输出图像的颜色映射匹配校准的dBZ范围,显示更准确
替代方案(直接用索引值校准)
如果不想转换为RGB,可直接提取图像的调色板,将图例颜色映射为对应的索引值,再进行校准。步骤如下:
- 读取图像时保留P模式:
img_pil = PIL.Image.open(fname),提取调色板:palette = img_pil.getpalette() - 将图例图像的RGB颜色转换为对应的索引值(找到调色板中最接近的颜色)
- 后续用索引值替代RGB通道进行距离计算
此方法适合追求性能的场景,但实现更复杂,推荐优先使用RGB转换方案。
内容的提问来源于stack exchange,提问作者Lucas
相关产品推荐
相关产品推荐

