You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

单通道雷达图像转数值校准失败,请求技术协助

解决单通道雷达图转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()

额外优化说明

  1. 将arrout的 dtype 改为np.float32:原代码用img.dtype(通常是uint8)会截断dBZ的浮点数值,导致精度丢失
  2. 手动设置vmin和vmax:让输出图像的颜色映射匹配校准的dBZ范围,显示更准确

替代方案(直接用索引值校准)

如果不想转换为RGB,可直接提取图像的调色板,将图例颜色映射为对应的索引值,再进行校准。步骤如下:

  • 读取图像时保留P模式:img_pil = PIL.Image.open(fname),提取调色板:palette = img_pil.getpalette()
  • 将图例图像的RGB颜色转换为对应的索引值(找到调色板中最接近的颜色)
  • 后续用索引值替代RGB通道进行距离计算
    此方法适合追求性能的场景,但实现更复杂,推荐优先使用RGB转换方案。

内容的提问来源于stack exchange,提问作者Lucas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 15:29:55