使用nlohmann/json序列化含cv::Mat的Camera类时出现段错误求助
解决nlohmann/json序列化cv::Mat及自定义Camera类的段错误问题
你的问题核心在于错误地尝试直接序列化cv::Mat的内部私有类型(cv::MatSize、cv::MatStep),并且对cv::Mat的序列化逻辑完全错误,导致了无限递归或非法内存访问,最终引发段错误。下面是具体的解决思路和完整代码:
为什么之前的写法会失败?
- cv::Mat的序列化逻辑错误:你在
adl_serializer<cv::Mat>中写了j = opt;,这会触发nlohmann/json再次尝试调用to_json来处理cv::Mat,形成无限递归,直接导致栈溢出或段错误。 - 不必要的内部类型序列化:
cv::MatSize和cv::MatStep是cv::Mat的内部辅助类型,它们和内存布局强绑定,根本不需要(也不应该)单独序列化。当我们正确序列化cv::Mat的可移植数据后,这些内部结构会自动被创建。
正确的解决方案:序列化cv::Mat的可移植数据
我们需要手动实现cv::Mat的序列化逻辑,将其可移植的核心数据(行数、列数、类型、像素数据)转换成json格式,反序列化时再根据这些数据重建cv::Mat。
步骤1:正确特化adl_serializercv::Mat
#include <nlohmann/json.hpp> #include <opencv2/opencv.hpp> namespace nlohmann { template<> struct adl_serializer<cv::Mat> { static void to_json(json& j, const cv::Mat& mat) { // 处理空Mat的特殊情况 if (mat.empty()) { j = json{{"empty", true}}; return; } // 存储cv::Mat的基本属性 j["rows"] = mat.rows; j["cols"] = mat.cols; j["type"] = mat.type(); // 将Mat的像素数据转换为vector存储(保证可移植性) std::vector<uchar> data_buffer; data_buffer.reserve(mat.total() * mat.elemSize()); if (mat.isContinuous()) { // 连续内存直接拷贝,效率更高 std::copy(mat.data, mat.data + mat.total() * mat.elemSize(), std::back_inserter(data_buffer)); } else { // 非连续内存逐行拷贝(比如ROI区域) for (int row = 0; row < mat.rows; ++row) { const uchar* row_ptr = mat.ptr<uchar>(row); std::copy(row_ptr, row_ptr + mat.cols * mat.elemSize(), std::back_inserter(data_buffer)); } } j["data"] = data_buffer; } static void from_json(const json& j, cv::Mat& mat) { // 重建空Mat if (j.contains("empty") && j["empty"].get<bool>()) { mat = cv::Mat(); return; } // 读取基本属性 int rows = j["rows"].get<int>(); int cols = j["cols"].get<int>(); int type = j["type"].get<int>(); std::vector<uchar> data_buffer = j["data"].get<std::vector<uchar>>(); // 重建cv::Mat,注意用clone()将数据拷贝到Mat的独立内存中 mat = cv::Mat(rows, cols, type, data_buffer.data()).clone(); } }; } // namespace nlohmann
步骤2:移除不必要的cv::MatSize和cv::MatStep序列化代码
直接删除你之前写的adl_serializer<cv::MatSize>和adl_serializer<cv::MatStep>特化,完全不需要它们。
步骤3:测试代码调整
你的测试代码中默认构造的Camera包含空的intrinsics_和distortion_,上面的序列化逻辑已经处理了空Mat的情况。如果要测试完整功能,可以初始化一个带有效数据的Camera:
int main() { // 创建测试用的相机参数 cv::Mat intrinsics = (cv::Mat_<double>(3,3) << 1000, 0, 320, 0, 1000, 240, 0, 0, 1); cv::Mat distortion = (cv::Mat_<double>(1,5) << 0.1, -0.05, 0, 0, 0); Camera camera("front_camera", "calibration_front.yml"); camera.intrinsics_ = intrinsics; camera.distortion_ = distortion; // 序列化 nlohmann::json j = camera; std::cout << "序列化结果:\n" << j.dump(4) << std::endl; // 反序列化 Camera camera2 = j.get<Camera>(); std::cout << "\n反序列化完成,相机名称:" << camera2.name_ << std::endl; std::cout << "内参矩阵行数:" << camera2.intrinsics_.rows << std::endl; return 0; }
关键注意点
- 避免直接赋值cv::Mat到json:必须手动拆解cv::Mat的可移植数据,不能依赖
j = mat这种写法。 - 处理空Mat:空Mat的序列化需要特殊判断,否则会访问空指针引发错误。
- clone()的使用:反序列化时用
clone()确保cv::Mat拥有独立的内存,避免依赖临时vector的生命周期。
内容的提问来源于stack exchange,提问作者Hannes
相关产品推荐
相关产品推荐

