异形热力图绘制:从柔性压力传感器数据到鞋垫压力分布可视化
2026/9/20 17:15:40 网站建设 项目流程

简介:针对柔性压力传感器阵列采集到的离散点数据,这份资源以鞋垫上的足底压力分布为实例,完整演示异形热力图绘制流程,适合生物力学、运动医学、智能穿戴设备设计等方向的研究者与开发人员参考。压缩包内共三个文件,核心是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,它提供nearestlinearcubic三种方法。我的测试结果是这样的:

方法速度平滑度对稀疏数据的适应性
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), method='linear' # 选 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,而不是imshowimshow适合规则矩形图像,它有自己的像素坐标体系,跟我们的毫米坐标不是天然对齐的;pcolormesh可以直接指定网格的 x/y 坐标,异形裁剪之后仍然能正确显示边界形状。用pcolormesh时,传入的 x、y 网格要比数据网格多一圈,因为pcolormesh绘制的是四边形面片,它的坐标定义在每个格点上。直接用grid_xgrid_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, cmap='viridis', shading='auto') ax.add_patch(plt.Polygon(insole_outline, fill=False, edgecolor='black', lw=1.5)) ax.set_aspect('equal') ax.set_xlabel('x (mm)') ax.set_ylabel('y (mm)') fig.colorbar(mesh, ax=ax, label='Pressure (kPa)')

注意shading='auto',这个参数会自动处理pcolormesh的维度问题,省去手动补边的麻烦。对于 0.5mm 网格,shading='auto'的表现足够好,省心很多。

5. 出图细节:配色、标尺和等压线

5.1 颜色映射的选择依据

Matplotlib 的默认viridis是个稳妥选择,它具备感知均匀特性,灰度打印后依然能分辨梯度变化,而且对色觉障碍人群也足够友好。很多人图方便用jet,但jet的亮度非单调变化,会在压力中等区域产生虚假的"视觉边界",容易误导观察者。在正式项目报告里,我一般用viridisturbo,后者的动态范围更大,在展示高对比度压力分布时有优势,但同样存在一定的视觉误导风险,需要结合场景判断。

颜色的下限和上限要固定,不能自动跟随每张图的最大最小值。如果一套数据里不同人踩出来的峰压不同,自动色标会导致两张同位置图颜色无法直接对比,一张红的区域可能是 80kPa,另一张红的区域可能只有 50kPa。固定色标范围后,跨样本对比才有意义。我通常先计算整批数据里第 5 百分位到第 95 百分位的值作为色标范围,以剔除极端异常值的影响。

5.2 传感器位置叠加和等压线

热力图本身已经很好用,但在工程汇报场合,我会额外做两件事:叠加传感器位置标记,以及绘制等压线。传感器位置叠加以空心圆点标出,能直观看出哪些区域有实测点、哪些区域完全依赖插值,帮助听者判断图面的可信度。等压线用ax.contour叠加,线宽我习惯设到 0.8,颜色用白色半透明,这样不会盖住底色,又能显示压力梯度。

sc = ax.scatter(positions[:, 0], positions[:, 1], c=pressure, cmap='viridis', edgecolor='white', linewidth=0.5, s=20, zorder=3) cs = ax.contour(grid_x, grid_y, masked_z.filled(np.nan), levels=8, colors='white', linewidths=0.8, alpha=0.6, zorder=4)

一个容易被忽视的细节是等压线不能直接用掩码数组画,contour对全 NaN 区域会报错或者画得很乱。我这里先masked_z.filled(np.nan),相当于还原成含 NaN 的普通数组,contour对 NaN 区域的处理是自动跳过的,效果等同于裁剪。

5.3 DPI、尺寸和保存格式

最后输出时,dpi建议不低于 300,figsize按鞋垫的长宽比调整,比如宽度 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 倍,否则会把传感器区域的信息也抹掉。

这组流程跑通之后,后续再做不同受试者的足底压力分析就轻松多了,数据换成新采集的数组,跑一遍脚本,图就能稳定生出来。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询