Python解析骑行fit文件:从数据读取到心率补全

张开发
2026/4/14 17:07:50 15 分钟阅读

分享文章

Python解析骑行fit文件:从数据读取到心率补全
1. 骑行数据处理的常见痛点作为一个骑行爱好者我经常遇到这样的问题辛辛苦苦骑完几十公里结果发现心率数据没记录上。上周就发生了这样的事 - 我带着Wahoo码表出门骑行却忘了戴心率带。回家后把fit文件导入骑行APP结果提示数据不完整。这种时候如果能用Python给fit文件补全心率数据就完美了。fit文件是Garmin等运动设备常用的数据格式它采用二进制存储结构紧凑但不易直接编辑。一个典型的骑行fit文件可能包含时间戳精确到秒经纬度坐标GPS轨迹海拔高度速度、踏频心率如果有连接心率带为什么需要处理fit文件我总结了几种常见场景设备故障导致部分数据缺失如心率带没电不同平台对数据完整性的要求不同Strava可能允许缺失心率而专业分析工具会报错需要合并多个数据源比如把手机记录的心率同步到码表记录的fit文件中2. 解析fit文件的基础操作2.1 安装必备工具链处理fit文件需要两个核心工具fitparsePython库用于读取和解析fit文件FitCSVToolGarmin官方工具实现fit与csv的互转安装方法很简单pip install fitparse对于FitCSVTool需要从Garmin开发者网站下载Java版本。我建议把它放在项目目录下的java文件夹中方便调用。2.2 读取文件内容差异我们先看看有心率和无心率的数据结构差异。假设有两个文件YES.fit有心率数据NO.fit无心率数据用以下代码可以对比输出import fitparse def print_fit_details(file_path): fitfile fitparse.FitFile(file_path) for record in fitfile.get_messages(record): print(f[{record.get(timestamp).value}]) for data in record: unit f {data.units} if data.units else print(f - {data.name}: {data.value}{unit}) print( 有心率文件 ) print_fit_details(YES.fit) print(\n 无心率文件 ) print_fit_details(NO.fit)典型输出差异会显示 有心率文件 [2023-07-15 08:30:00] - position_lat: 39.9042 - position_long: 116.4074 - altitude: 54.2 m - heart_rate: 142 bpm - cadence: 82 rpm 无心率文件 [2023-07-15 08:30:00] - position_lat: 39.9042 - position_long: 116.4074 - altitude: 54.2 m - cadence: 82 rpm3. 心率数据补全方案3.1 使用FitCSVTool转换格式直接编辑二进制fit文件风险很高更安全的做法是将fit转为csv可读性更好编辑csv文件将csv转回fit转换命令示例# fit转csv java -jar ./java/FitCSVTool.jar NO.fit # 会生成NO.csv # 编辑完成后csv转fit java -jar ./java/FitCSVTool.jar -c NO.csv NO_with_HR.fit3.2 生成合理的心率数据对于缺失的心率数据有几种生成策略固定值法设为平均心率如150bpm# 在csv中添加 heart_rate,150,bpm线性增长法模拟热身过程从120逐步上升到160真实数据移植从其他骑行记录中提取类似路段的心率我推荐第三种方法。假设你有同路线的心率数据import pandas as pd # 加载参考心率数据 ref_df pd.read_csv(YES.csv) hr_data ref_df[ref_df[name] heart_rate][[value, units]] # 应用到目标文件 target_df pd.read_csv(NO.csv) target_df pd.concat([target_df, hr_data], axis1) target_df.to_csv(NO_processed.csv, indexFalse)4. 完整处理流程实战4.1 数据验证与修复补全数据后需要验证合理性def validate_hr(fit_path): fitfile fitparse.FitFile(fit_path) hr_values [] for record in fitfile.get_messages(record): hr record.get(heart_rate) if hr: hr_values.append(hr.value) print(f心率范围: {min(hr_values)}-{max(hr_values)} bpm) print(f平均心率: {sum(hr_values)/len(hr_values):.1f} bpm) validate_hr(NO_with_HR.fit)4.2 异常处理建议在实际操作中可能会遇到时间戳不匹配需要同步两个文件的时间起点数据点数量不一致需要插值处理单位不一致如海拔单位是米还是英尺建议添加校验代码def check_consistency(original, processed): orig fitparse.FitFile(original) proc fitparse.FitFile(processed) orig_records list(orig.get_messages(record)) proc_records list(proc.get_messages(record)) assert len(orig_records) len(proc_records), 记录数不一致 print(基本校验通过)5. 进阶技巧与注意事项5.1 性能优化建议处理大型fit文件时如百公里骑行记录可以使用生成器避免内存爆炸def iter_fit(file_path): with open(file_path, rb) as f: fitfile fitparse.FitFile(f) yield from fitfile.get_messages()批量处理多个文件from concurrent.futures import ThreadPoolExecutor def process_file(path): # 处理逻辑 pass with ThreadPoolExecutor() as executor: executor.map(process_file, glob.glob(rides/*.fit))5.2 常见问题排查我遇到过的一些坑Java环境问题确保安装JRE 8并配置好PATH文件编码问题csv文件建议保存为UTF-8无BOM格式字段冲突不要修改csv中的message_index等关键字段最后提醒修改后的fit文件可能无法通过某些平台的严格校验如Strava的CRC检查建议先在测试平台验证后再提交重要记录。

更多文章