无锡网站建设公司,重庆建设工程造价网官网,视频网站建设服务,wordpress p标签C语言基础#xff1a;SDPose-Wholebody底层接口开发 想让人工智能看懂人的动作吗#xff1f;比如让健身APP自动纠正你的深蹲姿势#xff0c;或者让动画角色模仿真人的舞蹈动作。这背后都离不开一个核心技术#xff1a;人体姿态估计。 SDPose-Wholebody 就是目前这个领域里…C语言基础SDPose-Wholebody底层接口开发想让人工智能看懂人的动作吗比如让健身APP自动纠正你的深蹲姿势或者让动画角色模仿真人的舞蹈动作。这背后都离不开一个核心技术人体姿态估计。SDPose-Wholebody 就是目前这个领域里相当厉害的一个模型。它能从一张图片里精准地找出人体上133个关键点包括身体、手、脸和脚而且特别擅长处理那些“非主流”的图片比如油画、动漫里的人物。不过官方给的代码大多是Python的而且依赖一大堆深度学习框架。如果你想把SDPose塞进一个嵌入式设备里比如智能摄像头、机器人或者手机APP直接用Python那套就太重了。这时候我们就需要自己动手用C语言给它打造一个轻量级的“发动机”——也就是底层调用接口。这篇文章就是带你从零开始用C语言一步步把这个接口搭起来。即使你之前没怎么接触过深度学习模型部署跟着步骤走也能让SDPose在你的C程序里跑起来。1. 动手之前先搞清楚我们要做什么在开始写代码之前我们得先弄明白SDPose这个模型到底是怎么工作的以及我们最终要做出一个什么样的东西。简单来说SDPose是一个“自上而下”的模型。它干两件事找人先用一个检测模型比如YOLO在图片里把人的位置框出来。标点把框出来的人像区域送给SDPose模型让它预测出133个关键点的坐标。我们的目标就是写一个C语言的库能完成第二步“标点”的工作。这个库要能加载训练好的SDPose模型文件接收一张裁剪好的人像图片然后飞快地算出133个点的位置。听起来好像要处理很复杂的神经网络别担心我们不用从零实现整个模型。市面上已经有成熟的推理引擎比如ONNX Runtime、NCNN、TNN可以帮我们这个大忙。它们就像“万能翻译器”能把训练好的模型“翻译”成在各种硬件上都能高效运行的代码。我们的工作主要是用C语言去“指挥”这个翻译器。所以整个开发流程可以分成三步模型准备把官方的PyTorch模型转换成推理引擎能认识的格式比如ONNX。接口设计想好我们的C语言库应该长什么样有哪些函数怎么传数据。代码实现用C语言调用推理引擎把模型跑起来并把结果处理好。接下来我们就一步步走。2. 第一步把模型“打包”好官方的SDPose模型是用PyTorch训练的文件后缀通常是.pth。我们要先把它转换成更通用的格式。这里我们选择ONNX格式因为它被很多推理引擎支持是模型部署界的“普通话”。2.1 环境准备Python侧你需要一个有Python环境的地方比如你的开发电脑来执行转换。先安装必要的包pip install torch onnx onnx-simplifier # 如果需要还要安装SDPose官方的代码库 git clone https://github.com/t-s-liang/SDPose-OOD.git cd SDPose-OOD pip install -r requirements.txt2.2 编写模型转换脚本创建一个Python脚本比如叫export_to_onnx.py。这个脚本的核心是加载SDPose模型并把它“导出”成ONNX格式。import torch import torch.nn as nn from mmpose.models import build_posenet from mmcv import Config import onnx import onnxsim def export_sdpose_wholebody(): # 1. 加载模型配置文件需要根据SDPose官方仓库调整路径 cfg Config.fromfile(configs/sdpose/sdpose_wholebody.py) # 2. 构建模型结构 model build_posenet(cfg.model) # 3. 加载训练好的权重文件 (.pth) checkpoint torch.load(sdpose_wholebody.pth, map_locationcpu) if state_dict in checkpoint: state_dict checkpoint[state_dict] else: state_dict checkpoint model.load_state_dict(state_dict, strictFalse) model.eval() # 切换到评估模式 # 4. 准备一个模拟输入数据 # SDPose-Wholebody的输入是1024x768的RGB图像 dummy_input torch.randn(1, 3, 768, 1024) # (batch, channel, height, width) # 5. 导出模型到ONNX # 指定输入输出的名字以及动态维度batch_size可以是变化的 input_names [input_image] output_names [output_heatmaps] dynamic_axes { input_image: {0: batch_size}, output_heatmaps: {0: batch_size} } torch.onnx.export( model, dummy_input, sdpose_wholebody.onnx, input_namesinput_names, output_namesoutput_names, dynamic_axesdynamic_axes, opset_version13, # 使用一个较新的ONNX算子集版本 do_constant_foldingTrue ) print(ONNX model exported successfully.) # 6. (可选) 简化ONNX模型去除冗余操作 model_onnx onnx.load(sdpose_wholebody.onnx) model_simp, check onnxsim.simplify(model_onnx) assert check, Simplified ONNX model could not be validated onnx.save(model_simp, sdpose_wholebody_sim.onnx) print(ONNX model simplified successfully.) if __name__ __main__: export_sdpose_wholebody()注意这个脚本是个示例实际路径和配置需要根据SDPose-OOD官方仓库的具体结构进行调整。重点是理解导出ONNX的流程。运行这个脚本后你会得到sdpose_wholebody.onnx文件。这就是我们C语言程序要吃的“粮食”。3. 第二步设计我们的C语言接口现在模型准备好了我们来设计一下库长什么样。一个好的接口应该简单、清晰、好用。我们计划创建两个主要文件sdpose.h头文件声明所有函数和数据结构。sdpose.c源文件实现具体功能。先看看sdpose.h大概的样子#ifndef SDPOSE_H #define SDPOSE_H #ifdef __cplusplus extern C { #endif #include stdint.h // 定义关键点结构 (x, y, 置信度) typedef struct { float x; float y; float score; } KeyPoint; // 定义SDPose处理上下文用来保存模型、配置等状态 typedef void* SDPoseContext; /** * brief 初始化SDPose-Wholebody模型 * param model_path ONNX模型文件路径 * param use_gpu 是否使用GPU加速 (0: CPU, 1: GPU) * return 成功返回上下文句柄失败返回NULL */ SDPoseContext sdpose_init(const char* model_path, int use_gpu); /** * brief 执行姿态估计 * param ctx 上下文句柄 * param image_data 输入图像数据 (RGB格式大小为768x1024) * param width 图像宽度 (必须为1024) * param height 图像高度 (必须为768) * param keypoints 输出关键点数组 (需要预先分配133个KeyPoint的空间) * return 成功返回0失败返回错误码 */ int sdpose_predict(SDPoseContext ctx, const unsigned char* image_data, int width, int height, KeyPoint* keypoints); /** * brief 释放SDPose上下文资源 * param ctx 上下文句柄 */ void sdpose_release(SDPoseContext ctx); /** * brief 获取模型支持的输入图像尺寸 * param ctx 上下文句柄 * param width 返回宽度 * param height 返回高度 */ void sdpose_get_input_size(SDPoseContext ctx, int* width, int* height); /** * brief 获取关键点数量对于Wholebody模型固定为133 * return 关键点数量 */ int sdpose_get_num_keypoints(void); #ifdef __cplusplus } #endif #endif // SDPOSE_H这个设计非常直白sdpose_init开场加载模型搭好舞台。sdpose_predict主角登场输入图片输出133个点。sdpose_release演出结束打扫舞台。几个辅助函数用来查信息。用户用起来大概是这样#include sdpose.h #include stdio.h int main() { // 1. 初始化 SDPoseContext ctx sdpose_init(sdpose_wholebody_sim.onnx, 0); // 用CPU if (!ctx) { printf(初始化失败\n); return -1; } // 2. 准备图片数据 (这里需要你自己读取一张768x1024的RGB图片) int width, height; sdpose_get_input_size(ctx, width, height); unsigned char image_data[768 * 1024 * 3]; // 假设图片数据在这里 // 3. 准备存放结果的数组 KeyPoint keypoints[133]; // 4. 执行预测 int ret sdpose_predict(ctx, image_data, width, height, keypoints); if (ret ! 0) { printf(预测失败\n); sdpose_release(ctx); return -1; } // 5. 打印结果例如第一个关键点鼻子 printf(鼻子位置: (%.2f, %.2f), 置信度: %.2f\n, keypoints[0].x, keypoints[0].y, keypoints[0].score); // 6. 释放资源 sdpose_release(ctx); return 0; }接口设计好了接下来就是最核心的一步在sdpose.c里实现它。4. 第三步用C语言实现核心推理这里我们选择ONNX Runtime作为推理引擎。因为它跨平台支持CPU/GPU而且C语言的API比较完善。4.1 项目设置与依赖首先你需要下载ONNX Runtime的C语言库。可以去官网下载预编译包或者自己编译。假设你把头文件和库文件放在了onnxruntime/目录下。我们的项目结构如下your_project/ ├── onnxruntime/ # ONNX Runtime库 ├── sdpose.h ├── sdpose.c └── main.c # 测试用的主程序编译的时候需要链接ONNX Runtime的库。例如用gcc编译gcc -I./onnxruntime/include -L./onnxruntime/lib -o test_main main.c sdpose.c -lonnxruntime -lm4.2 实现 sdpose.c下面是sdpose.c的核心实现。我们会一步步来。#include sdpose.h #include stdlib.h #include string.h #include stdio.h // ONNX Runtime 头文件 #include onnxruntime_c_api.h // 我们内部用来保存所有状态的结构体 typedef struct { OrtEnv* env; // ONNX Runtime 环境 OrtSessionOptions* options; // 会话选项 OrtSession* session; // 推理会话 OrtMemoryInfo* memory_info; // 内存信息 int input_width; // 模型输入宽度 int input_height; // 模型输入高度 } SDPoseInternalContext; SDPoseContext sdpose_init(const char* model_path, int use_gpu) { SDPoseInternalContext* ctx (SDPoseInternalContext*)malloc(sizeof(SDPoseInternalContext)); if (!ctx) return NULL; memset(ctx, 0, sizeof(SDPoseInternalContext)); // 1. 初始化ONNX Runtime环境 OrtApi* ort OrtGetApiBase()-GetApi(ORT_API_VERSION); OrtStatus* status; status ort-CreateEnv(ORT_LOGGING_LEVEL_WARNING, SDPose, ctx-env); if (status) { ort-ReleaseStatus(status); free(ctx); return NULL; } // 2. 创建会话选项 status ort-CreateSessionOptions(ctx-options); if (status) { ort-ReleaseStatus(status); ort-ReleaseEnv(ctx-env); free(ctx); return NULL; } // 设置一些优化选项 ort-SetIntraOpNumThreads(ctx-options, 1); // 设置计算线程数 ort-SetInterOpNumThreads(ctx-options, 1); // 3. 如果使用GPU配置GPU执行 provider if (use_gpu) { // 注意需要检查ONNX Runtime是否编译了GPU支持 OrtSessionOptionsAppendExecutionProvider_CUDA(ctx-options, 0); } // 4. 创建会话加载模型 status ort-CreateSession(ctx-env, model_path, ctx-options, ctx-session); if (status) { ort-ReleaseStatus(status); ort-ReleaseSessionOptions(ctx-options); ort-ReleaseEnv(ctx-env); free(ctx); return NULL; } // 5. 创建内存信息用于分配输入输出张量 status ort-CreateCpuMemoryInfo(OrtArenaAllocator, OrtMemTypeDefault, ctx-memory_info); if (status) { ort-ReleaseStatus(status); ort-ReleaseSession(ctx-session); ort-ReleaseSessionOptions(ctx-options); ort-ReleaseEnv(ctx-env); free(ctx); return NULL; } // 6. 获取模型的输入信息确定输入尺寸 // 这里我们假设模型只有一个输入且我们知道SDPose-Wholebody是1024x768 // 更严谨的做法是动态获取这里为了简化先写死 ctx-input_width 1024; ctx-input_height 768; printf(SDPose模型初始化成功\n); return (SDPoseContext)ctx; }初始化部分完成了重点是创建ONNX Runtime的会话。接下来是实现预测函数sdpose_predict。这是最复杂的一部分因为要处理数据格式的转换。int sdpose_predict(SDPoseContext ctx, const unsigned char* image_data, int width, int height, KeyPoint* keypoints) { SDPoseInternalContext* internal_ctx (SDPoseInternalContext*)ctx; if (!internal_ctx || !image_data || !keypoints) return -1; // 检查输入尺寸是否符合模型要求 if (width ! internal_ctx-input_width || height ! internal_ctx-input_height) { printf(错误输入图像尺寸必须为 %dx%d当前为 %dx%d\n, internal_ctx-input_width, internal_ctx-input_height, width, height); return -2; } OrtApi* ort OrtGetApiBase()-GetApi(ORT_API_VERSION); OrtStatus* status; // 1. 准备输入数据 // 模型输入 [1, 3, 768, 1024], 数据类型 float size_t input_tensor_size 1 * 3 * height * width; float* input_tensor_values (float*)malloc(input_tensor_size * sizeof(float)); if (!input_tensor_values) return -3; // 将 uint8 的 RGB 数据 [0-255] 转换为 float并做归一化假设除以255 // 注意实际SDPose可能需要特定的归一化方式如减均值除标准差这里需要根据模型调整 for (int c 0; c 3; c) { for (int h 0; h height; h) { for (int w 0; w width; w) { // 内存布局RGBRGBRGB... 转为 CHW (Channel, Height, Width) int src_idx (h * width w) * 3 c; // 原始图像索引 int dst_idx c * height * width h * width w; // 张量索引 input_tensor_values[dst_idx] image_data[src_idx] / 255.0f; } } } // 2. 创建输入张量 const int64_t input_shape[] {1, 3, height, width}; // NCHW OrtValue* input_tensor NULL; status ort-CreateTensorWithDataAsOrtValue( internal_ctx-memory_info, input_tensor_values, input_tensor_size * sizeof(float), input_shape, 4, // 维度数量 ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT, input_tensor ); // 3. 准备输出张量 // 模型输出热图 [1, 133, H, W]这里H和W是输入尺寸的1/4192x256 OrtValue* output_tensor NULL; // 4. 运行推理 const char* input_names[] {input_image}; const char* output_names[] {output_heatmaps}; status ort-Run(internal_ctx-session, NULL, input_names, (const OrtValue* const*)input_tensor, 1, output_names, 1, output_tensor); // 5. 处理输出提取关键点坐标 if (!status output_tensor) { // 获取输出张量数据 float* output_data NULL; OrtTensorTypeAndShapeInfo* info NULL; status ort-GetTensorMutableData(output_tensor, (void**)output_data); if (!status) { status ort-GetTensorTypeAndShape(output_tensor, info); } if (!status info) { // 获取输出热图的尺寸 size_t num_dims; ort-GetDimensionsCount(info, num_dims); int64_t* dims (int64_t*)malloc(num_dims * sizeof(int64_t)); ort-GetDimensions(info, dims, num_dims); // 对于SDPose-Wholebody输出 dims 应该是 [1, 133, 192, 256] int heatmap_h (int)dims[2]; int heatmap_w (int)dims[3]; // 6. 从热图中解析出关键点坐标 (使用argmax) for (int kp 0; kp 133; kp) { float max_val -1.0f; int max_h 0, max_w 0; // 在单个关键点的热图上找最大值位置 for (int h 0; h heatmap_h; h) { for (int w 0; w heatmap_w; w) { int idx kp * heatmap_h * heatmap_w h * heatmap_w w; if (output_data[idx] max_val) { max_val output_data[idx]; max_h h; max_w w; } } } // 将热图坐标映射回原图坐标 (因为热图是下采样后的) // 使用SDPose中常用的Unbiased Data Processing (UDP) 映射方式 keypoints[kp].x (max_w 0.5f) * (width / (float)heatmap_w); keypoints[kp].y (max_h 0.5f) * (height / (float)heatmap_h); keypoints[kp].score max_val; } free(dims); ort-ReleaseTensorTypeAndShapeInfo(info); } } // 7. 清理资源 if (input_tensor) ort-ReleaseValue(input_tensor); if (output_tensor) ort-ReleaseValue(output_tensor); free(input_tensor_values); if (status) { ort-ReleaseStatus(status); return -4; // 推理失败 } return 0; // 成功 }最后实现剩下的几个辅助和清理函数void sdpose_release(SDPoseContext ctx) { if (!ctx) return; SDPoseInternalContext* internal_ctx (SDPoseInternalContext*)ctx; OrtApi* ort OrtGetApiBase()-GetApi(ORT_API_VERSION); if (internal_ctx-memory_info) ort-ReleaseMemoryInfo(internal_ctx-memory_info); if (internal_ctx-session) ort-ReleaseSession(internal_ctx-session); if (internal_ctx-options) ort-ReleaseSessionOptions(internal_ctx-options); if (internal_ctx-env) ort-ReleaseEnv(internal_ctx-env); free(internal_ctx); } void sdpose_get_input_size(SDPoseContext ctx, int* width, int* height) { if (!ctx || !width || !height) return; SDPoseInternalContext* internal_ctx (SDPoseInternalContext*)ctx; *width internal_ctx-input_width; *height internal_ctx-input_height; } int sdpose_get_num_keypoints(void) { return 133; // SDPose-Wholebody 固定输出133个关键点 }5. 第四步试试看让它跑起来代码写完了我们写个简单的main.c来测试一下。由于搞到一张正好768x1024的测试图有点麻烦这里我们用一个生成模拟图片数据的例子。#include sdpose.h #include stdio.h #include stdlib.h #include time.h // 生成一个简单的模拟图像中间一个白色矩形模拟一个人形区域 void generate_dummy_image(unsigned char* data, int width, int height) { for (int h 0; h height; h) { for (int w 0; w width; w) { int idx (h * width w) * 3; // 在图像中心画一个矩形 if (w width/4 w 3*width/4 h height/4 h 3*height/4) { data[idx] 200; // R data[idx1] 200; // G data[idx2] 200; // B } else { data[idx] 50; // R data[idx1] 50; // G data[idx2] 50; // B } } } } int main() { printf( SDPose-Wholebody C接口测试 \n); // 1. 初始化模型 SDPoseContext ctx sdpose_init(sdpose_wholebody_sim.onnx, 0); if (!ctx) { printf(模型初始化失败请检查模型路径。\n); return 1; } // 2. 获取模型要求的输入尺寸 int width, height; sdpose_get_input_size(ctx, width, height); printf(模型输入尺寸: %d x %d\n, width, height); // 3. 准备图像数据 unsigned char* image_data (unsigned char*)malloc(width * height * 3); if (!image_data) { printf(内存分配失败\n); sdpose_release(ctx); return 1; } generate_dummy_image(image_data, width, height); printf(模拟图像数据生成完毕。\n); // 4. 准备关键点数组 KeyPoint keypoints[133]; // 5. 执行推理并计时 clock_t start clock(); int ret sdpose_predict(ctx, image_data, width, height, keypoints); clock_t end clock(); if (ret ! 0) { printf(姿态估计失败错误码: %d\n, ret); } else { double time_used ((double)(end - start)) / CLOCKS_PER_SEC; printf(推理成功耗时: %.3f 秒\n, time_used); // 6. 打印前5个关键点的信息通常是身体中轴线 printf(\n关键点示例 (前5个):\n); const char* kp_names[] {鼻子, 左眼, 右眼, 左耳, 右耳}; // 示例名称 for (int i 0; i 5 i 133; i) { printf( %s: (%.1f, %.1f), 置信度: %.3f\n, kp_names[i], keypoints[i].x, keypoints[i].y, keypoints[i].score); } // 7. 可以简单统计一下高置信度的点有多少 int high_conf_points 0; for (int i 0; i 133; i) { if (keypoints[i].score 0.5) high_conf_points; } printf(\n置信度 0.5 的关键点有 %d 个。\n, high_conf_points); } // 8. 清理 free(image_data); sdpose_release(ctx); printf(\n测试完成。\n); return 0; }编译并运行这个测试程序。如果一切顺利你会看到模型加载成功并输出推理时间和几个关键点的坐标。虽然我们用的是模拟图片但整个流程是通的。6. 还能做得更好下一步优化建议让基础版本跑起来只是第一步。在实际项目中你可能还需要考虑下面这些事预处理要精确上面代码里图片归一化只是简单除以255。但SDPose模型在训练时可能有特定的均值和标准差需要减除。你需要去官方代码里查一下数据预处理的具体参数把它补上。后处理要完善我们只是简单用了热图argmax。SDPose论文里提到了使用UDPUnbiased Data Processing来获得更精确的坐标。你可以实现更精细的坐标解码比如用高斯拟合热图峰值周围的区域。性能要提升用模拟数据测一下速度。如果觉得慢可以尝试启用GPU加速在sdpose_init里传use_gpu1并确保ONNX Runtime支持CUDA。使用ONNX Runtime的更多优化选项如图算子融合、使用更快的执行provider如TensorRT, OpenVINO。考虑对输入图片进行批量处理batch inference一次处理多张图。内存要管理我们的接口要求用户分配好输出数组。对于更复杂的应用可以考虑让库内部管理内存提供sdpose_create_keypoints_array和sdpose_free_keypoints_array这样的函数。错误处理要健壮现在的错误处理还比较基础。可以定义更详细的错误码并在函数失败时提供日志信息。封装成更独立的库把ONNX Runtime的依赖打包好提供更简单的编译方式比如做成一个libsdpose.a静态库让其他项目更容易集成。走完上面这些步骤你应该就有了一个能在嵌入式环境或高性能C/C服务中直接调用的SDPose引擎了。它摆脱了Python的重量级依赖可以更紧密地集成到你的产品中去驱动那些需要实时理解人体动作的智能应用。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。