
简介针对柔性压力传感器阵列采集到的离散点数据这份资源以鞋垫上的足底压力分布为实例完整演示异形热力图绘制流程适合生物力学、运动医学、智能穿戴设备设计等方向的研究者与开发人员参考。压缩包内共三个文件核心是Python绘图脚本配套初始测试图与最终输出图总体积约六十八KB。脚本借助第三方库完成传感器数据筛选、插值和归一化将离散点映射到鞋垫二维平面后填充为连续压力分布生成能直观辨别压力集中区域的热力图测试图与输出图可对照验证每一步处理效果帮助理解不规则区域可视化中数据映射与插值的实现细节。这一过程覆盖了从原始数据清洗到图像输出的完整链路代码结构清晰稍作修改即可迁移到其他阵列式传感器数据。已有二百二十九人学习对于正在处理非规则区域离散数据、希望借鉴成熟做法的读者是份不错的参考资料且能快速掌握基于Python的不规则热力图生成思路。 第一次做完鞋垫压力采集我拿到一组传感器原始数据满心以为一把imshow就能出图结果得到一张标准的矩形色块图边缘全是空白形状跟鞋垫八竿子打不着。后来跟做可穿戴设备的朋友聊发现大家都会卡在同一个点上传感器是离散的、排列还是不规则的鞋垫本身又是异形轮廓常规画图工具压根不认。这篇文章就把我后来摸索出的异形热力图绘制流程拆开讲清楚从数据坐标化、离散点插值再到轮廓裁剪出图每一步都给出可以直接用的方案和代码给同样在处理柔性压力传感器阵列数据的同学做个参考。1. 为什么矩形热力图在鞋垫数据上行不通先说说最朴素的思路为什么会翻车。柔性压力传感器阵列铺在鞋垫上形状是跟着鞋垫走的脚掌宽的地方传感器排得多脚弓位置通常没有传感器后跟区域又是一个接近圆形的分布。这种布局在物理上是贴合足底的但如果直接按行列号建立二维数组数组是矩形的传感器缺失的地方就成了数组里的空洞或无效值画出来的热力图自然也是矩形的。另一个问题是离散性。无论传感器阵列密度做到多高单只鞋垫上几十个到上百个测点都称不上连续场——压力分布在空间上是连续变化的传感器只在这些离散位置给了采样值。热力图要好看、要用在论文或演示里必须把离散量插值成连续的颜色过渡这一步不处理图面就是一堆色块拼接缺乏连续感。更关键的是坐标基准。热力图的本质是某个空间位置对应某个值如果传感器位置都没有被数字化后面的一切都无从谈起。很多初学者拿到的是通道号和数据列表却不清楚每个通道对应的物理位置结果图能画出来但位置全错。这里必须建立一个清晰的流程传感器坐标数字化、压力值匹配、空间插值、边界裁剪、可视化呈现。异形这两个字其实包含两层意思一是数据点在空间上是不规则离散的二是热力图要呈现的区域本身是非矩形边界。我的实践顺序是先做一块规则的稠密网格把离散数据插值到这块网格上再用鞋垫轮廓去裁剪网格最后绘制出图的。这个顺序的好处是每一步都能独立验证出了问题也好定位。2. 准备数据先给每个传感器一个真实坐标2.1 从通道编号到物理坐标的换算假设你手里的传感器阵列是 8 行 16 列这样的编号结构但物理上传感器并不是等间距的矩形网格。柔性基底在制造过程中会有拉伸传感器的实际间距可能每行不同鞋垫边缘的传感器还会被裁剪掉一部分实际有效通道少于理论行列数。这时候不能直接拿行列号当坐标必须做一次通道号到物理坐标的映射。我的做法是在项目中把真实坐标单独维护成一个数组。比如最外侧第 0 行的传感器实际 x 方向间距是 9.8mm第 7 行变成 10.5mm这些数据可以在拿到传感器阵列后用直尺或游标卡尺量几个关键间距中间值按线性变化估算。有了真实间距后建立起一个pos数组形状是(N, 2)第一列是 x 坐标第二列是 y 坐标N 是有效通道数。如果传感器厂家提供了 CAD 图或阵列坐标表那就更省事直接导入就行。实际项目中还有一种更高效的方案用一台扫描仪把鞋垫的透明薄膜带传感器布局扫成图片然后在图上手动标记每个传感器中心点坐标通过像素坐标和实际尺寸的比例换算成毫米坐标。这个方法虽然前期标记工作量大但精度足够而且天然包含了传感器被裁剪后的实际位置信息。import numpy as np # 示例有效通道列表每个元素是 (x_mm, y_mm, channel_id) positions [] for ch in sensor_channels: x_mm logic_to_x(ch.row, ch.col) # 按实测间距换算 y_mm logic_to_y(ch.row, ch.col) positions.append([x_mm, y_mm]) positions np.array(positions) # shape: (n_sensors, 2)注意坐标单位统一用毫米后续所有几何操作都用同一套单位别混着用像素值和毫米值这是我在第一个项目里栽过的跟头。2.2 数据对齐与标定的必要性传感器采集到的是电压或电阻变化要画压力热力图必须先转换成压力值。这一步很多人会跳过直接把原始值怼进图里结果不同人踩上去的图片颜色范围差异极大没法横向比较。我常用的做法是用一组砝码做静态标定记录不同压强下传感器的响应值然后做一条拟合曲线把电压值映射到 kPa。多数压阻式传感器的响应是非线性的二阶多项式或者对数拟合在低压段表现比较好高压段勉强能用具体看传感器类型。标定完成后你会得到一个一维压力数组pressure长度跟positions一致。在进入绘图之前我还习惯做一步清洗检查是否有悬空值、断连值和明显的野值。柔性传感器弯折次数多了个别通道会出现漂移或者直接失效这些点的值会严重影响插值结果尤其是失效点会给周围区域带来错误的压力峰值。清洗策略我建议保守一点不要直接删掉失效点而是把它们的值标记为 NaN。原因在于删掉点会改变相邻点之间的空间距离关系而标记 NaN 的话后续插值工具可以明确识别这些位置没有测量值在可视化时就能以无数据处理而不是伪造成一个很大的压力峰。3. 从离散点到连续场插值方法怎么选3.1 三种常用插值方法的实际表现有了坐标和压力值接下来要做的是把离散点插值到规则网格上。科学计算里最常用的插值函数是scipy.interpolate.griddata它提供nearest、linear、cubic三种方法。我的测试结果是这样的方法速度平滑度对稀疏数据的适应性nearest最快差色块感强稳定不会出现明显异常值linear快中等有棱角相对稳定局部可能不够光滑cubic较慢平滑度高数据太稀疏时容易出现过冲和震荡在鞋垫这个场景下传感器数量通常只有几十个属于比较稀疏的数据集。cubic方法在点与点之间会产生自然的平滑过渡视觉上最好看但它会在点之间的空白区域生成一些超出实际压力范围的极值这在压力分布分析中是很危险的。比如某两个相邻点压力分别是 20kPa 和 100kPa中间的差值可能被插值成 150kPa这显然不合理。用linear虽然棱角明显但数值严格受限于相邻点的范围物理含义更可解释。如果一定要平滑效果我建议先用linear插值再对结果做一次小半径高斯滤波把棱角磨平而不是直接上cubic。另外还有个容易忽略的细节griddata的输入要求是点的坐标列表和点的值列表输出的是网格坐标处的插值结果。网格通常用np.meshgrid生成但griddata在内部会把网格坐标展平得到结果后再reshape回二维。这块要非常小心很多人做的时候就漏了reshape导致后续裁剪和绘图全部错位。3.2 网格密度与边界区域的取舍网格密度决定了图的分辨率但也不是越密越好。以一只鞋垫的尺寸大约 280mm × 100mm 为例我常用 0.5mm 或 1mm 的网格间距对应网格尺寸(281, 101)或(141, 51)。这个密度肉眼看起来已经很细腻计算量也很小。如果传感器间距本身接近 10mm网格密度超过传感器间距的十倍意义不大图面的细节不会增加只会让插值算法在传感器之间过度发挥。边界区域是插值重灾区。鞋垫边缘外通常没有传感器插值算法会怎么处理griddata对凸包外的点会返回 NaN这是默认行为。鞋垫本身是个不规则凸形如果直接用鞋垫轮廓内部的网格点去查询插值落在轮廓内但超出传感器凸包的点还是会得到 NaN。这部分区域的处理策略会直接影响到后面裁剪的效果。我是这么做的先做一个初始插值把所有 NaN 区域用最近邻方法填上再做一次裁剪这样边界处的颜色虽然只是一种估计但不会出现空白缺口视觉上更完整。from scipy.interpolate import griddata grid_x, grid_y np.meshgrid( np.arange(0, 280.5, 0.5), np.arange(0, 100.5, 0.5) ) grid_z griddata( positions, pressure, (grid_x, grid_y), methodlinear # 选 linear 保证数值安全 )4. 异形裁剪让热力图真正长在鞋垫轮廓上这一步是整个流程的核心也是最容易跟通用热力图教程脱节的地方。目标很明确只保留鞋垫轮廓内部的像素轮廓外全部设为遮罩不显示。4.1 轮廓数据的获取鞋垫轮廓从哪来最直接的方式是拿一张干净的鞋垫扫描图提取边界点列。也可以用矢量软件手动描边导出一系列闭合点的坐标。精度上不需要太高轮廓点间隔在几毫米内就够了因为我们的网格间距是 0.5mm轮廓插值之后足够平滑。获取到轮廓点后用matplotlib.path.Path构建闭合多边形然后逐一判断网格点是否落在多边形内部。这里建议用Path.contains_points它支持批量查询直接传入整个网格点的坐标数组即可效率很高。from matplotlib.path import Path # insole_path: 鞋垫轮廓的闭合点列表 (M, 2) shoe_path Path(insole_outline) points np.column_stack([grid_x.ravel(), grid_y.ravel()]) inside shoe_path.contains_points(points).reshape(grid_x.shape)到这里就拿到了一个布尔掩码inside尺寸跟网格一致。接下来用它把轮廓外的值全部置为 NaN这样绘图时就不会显示轮廓外的插值结果。4.2 掩码叠加与最终绘制绘制这一步我用的是pcolormesh而不是imshow。imshow适合规则矩形图像它有自己的像素坐标体系跟我们的毫米坐标不是天然对齐的pcolormesh可以直接指定网格的 x/y 坐标异形裁剪之后仍然能正确显示边界形状。用pcolormesh时传入的 x、y 网格要比数据网格多一圈因为pcolormesh绘制的是四边形面片它的坐标定义在每个格点上。直接用grid_x和grid_y会少一行一列需要手动补边。import matplotlib.pyplot as plt # 补一圈坐标适配 pcolormesh 的要求 x_edge np.concatenate([grid_x[:, 0] - 0.25, grid_x[0, :]]) y_edge np.concatenate([grid_y[0, :] - 0.25, grid_y[:, 0]])更省事的方式是直接沿用插值后 griddata 返回的形状用pcolormesh(grid_x, grid_y, masked_data)其中masked_data是把轮廓外部分用np.ma.masked_where遮掉后的结果。掩码数组会告诉 matplotlib 哪些区域不需要绘制图面上就是透明背景。masked_z np.ma.masked_where(~inside, grid_z) fig, ax plt.subplots(figsize(8, 4)) mesh ax.pcolormesh(grid_x, grid_y, masked_z, cmapviridis, shadingauto) ax.add_patch(plt.Polygon(insole_outline, fillFalse, edgecolorblack, lw1.5)) ax.set_aspect(equal) ax.set_xlabel(x (mm)) ax.set_ylabel(y (mm)) fig.colorbar(mesh, axax, labelPressure (kPa))注意shadingauto这个参数会自动处理pcolormesh的维度问题省去手动补边的麻烦。对于 0.5mm 网格shadingauto的表现足够好省心很多。5. 出图细节配色、标尺和等压线5.1 颜色映射的选择依据Matplotlib 的默认viridis是个稳妥选择它具备感知均匀特性灰度打印后依然能分辨梯度变化而且对色觉障碍人群也足够友好。很多人图方便用jet但jet的亮度非单调变化会在压力中等区域产生虚假的视觉边界容易误导观察者。在正式项目报告里我一般用viridis或turbo后者的动态范围更大在展示高对比度压力分布时有优势但同样存在一定的视觉误导风险需要结合场景判断。颜色的下限和上限要固定不能自动跟随每张图的最大最小值。如果一套数据里不同人踩出来的峰压不同自动色标会导致两张同位置图颜色无法直接对比一张红的区域可能是 80kPa另一张红的区域可能只有 50kPa。固定色标范围后跨样本对比才有意义。我通常先计算整批数据里第 5 百分位到第 95 百分位的值作为色标范围以剔除极端异常值的影响。5.2 传感器位置叠加和等压线热力图本身已经很好用但在工程汇报场合我会额外做两件事叠加传感器位置标记以及绘制等压线。传感器位置叠加以空心圆点标出能直观看出哪些区域有实测点、哪些区域完全依赖插值帮助听者判断图面的可信度。等压线用ax.contour叠加线宽我习惯设到 0.8颜色用白色半透明这样不会盖住底色又能显示压力梯度。sc ax.scatter(positions[:, 0], positions[:, 1], cpressure, cmapviridis, edgecolorwhite, linewidth0.5, s20, zorder3) cs ax.contour(grid_x, grid_y, masked_z.filled(np.nan), levels8, colorswhite, linewidths0.8, alpha0.6, zorder4)一个容易被忽视的细节是等压线不能直接用掩码数组画contour对全 NaN 区域会报错或者画得很乱。我这里先masked_z.filled(np.nan)相当于还原成含 NaN 的普通数组contour对 NaN 区域的处理是自动跳过的效果等同于裁剪。5.3 DPI、尺寸和保存格式最后输出时dpi建议不低于 300figsize按鞋垫的长宽比调整比如宽度 8 英寸、高度按比例 3.5 英寸左右这样图面不会被拉伸变形。保存格式上论文插图用 PDF网页演示用 PNG。PDF 是矢量格式局部放大的时候清晰度不会掉。6. 实际项目中容易踩的坑坑这个东西写出来总比读者自己踩一遍强。这几种情况我在项目里都遇到过各位做的时候可以提前避开。6.1 悬空传感器导致的颜色异常鞋垫在脚弓处往往没有完整贴合足底这里的传感器处于半接触或全悬空状态数值低且不稳定。插值之后如果某个悬空点的值只有 2kPa而周围足弓边缘的插值点被算到 40kPa就会出现一个大陡坡图上看就像足弓处有个色块断层。我的处理方法是把明显悬空的通道排除在插值之外只保留真正贴合点做插值否则足弓区域会被错误地填上压力值。6.2 左右脚坐标镜像左右脚鞋垫的轮廓是镜面对称的如果你的代码把坐标写死了拿右脚数据跑左脚轮廓画出来的图会是形状对但位置反足跟的压力峰会出现在前方。这不是颜色问题是坐标轴方向问题。建议在数据结构里就存好side字段加载轮廓时根据左右脚自动选择对应的轮廓文件彻底杜绝镜像污染。6.3 插值过冲导致超出物理范围前面提到 cubic 容易过冲这里说个具体例子。有一组数据前掌两个传感器间距约 15mm压力分别测得 60kPa 和 120kPa。linear 插值之后两点中点大概是 90kPa合理但 cubic 可能算出 140kPa甚至还出现一个 30kPa 的波谷这完全违背物理实际。所以我始终坚持用 linear 插值配合轻微平滑而不是直接用 cubic。如果真需要很平滑的表面就做高斯滤波半径不要超过网格间距的 1.5 倍否则会把传感器区域的信息也抹掉。这组流程跑通之后后续再做不同受试者的足底压力分析就轻松多了数据换成新采集的数组跑一遍脚本图就能稳定生出来。本文还有配套的精品资源点击获取