当前位置: 首页 > 面试题库 >

如何在Java中优雅地序列化和反序列化OpenCV YAML校准数据?

叶允晨
2023-03-14
问题内容

我正在尝试使用官方OpenCV Java绑定以YAML格式加载/保存OpenCV校准数据。我知道OpenCV(至少为c
++版本)可以序列化为XML和JSON,但是我想支持较早的YAML校准文件。

校准文件如下所示:

%YAML:1.0
cameraMatrix: !!opencv-matrix
   rows: 3
   cols: 3
   dt: d
   data: [ 6.6278599887122368e+02, 0., 3.1244256016006659e+02, 0.,
       6.6129276875199082e+02, 2.2747179767124251e+02, 0., 0., 1. ]
imageSize_width: 640
imageSize_height: 480
sensorSize_width: 0
sensorSize_height: 0
distCoeffs: !!opencv-matrix
   rows: 5
   cols: 1
   dt: d
   data: [ -1.8848338341464690e-01, 1.0721890419183855e+00,
       -3.5244467228016116e-03, -7.0195032848241403e-04,
       -2.0412827999027101e+00 ]
reprojectionError: 2.1723265945911407e-01

我正在寻找一个优雅的解决方案,因为我不太了解如何将Java类最好地映射到YAML并返回。我尝试了一些库,例如jyaml,yamlbeans(来自SourceForge的1.0和来自MavenCentral的1.13)和SnakeYAML。

我目前尝试对一些作品进行反序列化,但感觉很棘手:

CalibrationParseTest.java

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;

import org.opencv.core.Core;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;

public class CalibrationParseTest {

    public static void main(String[] args) {
        // load OpenCV native
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);

        String yamlPath = "./data/calibration.yml";

        try{

          String yamlString = new String(Files.readAllBytes(Paths.get(yamlPath)), StandardCharsets.UTF_8);
          // remove %YAML:1.0 to avoid scan directive error
          yamlString = yamlString.replaceAll("%YAML:1.0", "");
          // map custom class
          yamlString = yamlString.replaceAll("opencv-matrix", "MatYAML");

          System.out.println("<loaded>");
          System.out.println(yamlString);
          System.out.println("</loaded>");

          Yaml yaml = new Yaml(new Constructor(CalibrationData.class));
          CalibrationData data = yaml.load(yamlString);
          // currently manually parsing data from the HashMap: can this be better ?
          data.populateCV();
          // double check data
          System.out.println("<deserialized>");
          System.out.println(data);
          System.out.println("</deserialized>");

        }catch (IOException e) { 
          e.printStackTrace(); 
        } 
    }

}

CalibrationData.java

import java.util.HashMap;

import org.opencv.core.Mat;
import org.opencv.core.Size;

public class CalibrationData extends HashMap{

    public Mat cameraMatrix;
    public Size imageSize;
    public Size sensorSize;
    public Mat distCoeffs;
    public float reprojectionError;

    public CalibrationData(){}

    public void populateCV(){
        cameraMatrix      = ((MatYAML)get("cameraMatrix")).toMat();
        imageSize         = new Size((int)get("imageSize_width"),(int)get("imageSize_height"));
        sensorSize        = new Size((int)get("sensorSize_width"),(int)get("sensorSize_height"));
        distCoeffs        = ((MatYAML)get("distCoeffs")).toMat();
        reprojectionError = (float)((double)get("reprojectionError"));
    }

    public String toString(){
        if(cameraMatrix == null){
            return String.format("[CalibrationData (not parsed to CV-> call populateCV()\n\tdata: %s\n]",super.toString());
        }
        return String.format("[CalibrationData\n" + 
                             "\tcalibrationMatrix: %s\n" + 
                             "\timageSize: %s\n" + 
                             "\tsensorSize: %s\n" + 
                             "\tdistCoeffs: %s\n" + 
                             "\treprojectionError: %f\n]", cameraMatrix.dump(), imageSize.toString(), sensorSize.toString(), distCoeffs.dump(), reprojectionError);
    }

}

MatYAML.java

import java.util.List;

import org.opencv.core.CvType;
import org.opencv.core.Mat;

public class MatYAML{

    public int rows;
    public int cols;
    public String dt;
    public List<Double> data;

    Mat toMat(){
        Mat out = new Mat(rows, cols, dt.equals("d") ? CvType.CV_64F : CvType.CV_32F);

        int index = 0;

        for (int row = 0; row < rows; row++) {
            for (int col = 0; col < cols; col++) {
                out.put(row, col, data.get(index++));
            }
        }

        return out;
    }

}

这将输出预期结果:

<loaded>

cameraMatrix: !!MatYAML
   rows: 3
   cols: 3
   dt: d
   data: [ 6.6278599887122368e+02, 0., 3.1244256016006659e+02, 0.,
       6.6129276875199082e+02, 2.2747179767124251e+02, 0., 0., 1. ]
imageSize_width: 640
imageSize_height: 480
sensorSize_width: 0
sensorSize_height: 0
distCoeffs: !!MatYAML
   rows: 5
   cols: 1
   dt: d
   data: [ -1.8848338341464690e-01, 1.0721890419183855e+00,
       -3.5244467228016116e-03, -7.0195032848241403e-04,
       -2.0412827999027101e+00 ]
reprojectionError: 2.1723265945911407e-01

</loaded>
<deserialized>
[CalibrationData
    calibrationMatrix: [662.7859988712237, 0, 312.4425601600666;
  0, 661.2927687519908, 227.4717976712425;
  0, 0, 1]
    imageSize: 640x480
    sensorSize: 0x0
    distCoeffs: [-0.1884833834146469; 1.072189041918385; -0.003524446722801612; -0.000701950328482414; -2.04128279990271]
    reprojectionError: 0.217233
]
</deserialized>

没有这些技巧,是否有更优雅的方法在Java OpenCV类和YAML之间进行序列化/反序列化?

骇客是指:

  • 手动删除yaml版本指令
  • 用MatYAML字符串交换opencv-matrix
  • 手动转换HashMap值
  • 可能避免手动填充OpenCV Mat数据?(如果可能的话 ?)

Update 2 amanin的答案更干净,可以避免过分地替换“ !! opencv-matrix”,但是它不会序列化/反序列化Mat

OpenCVConfig{imageSize_width=640, imageSize_height=480, sensorSize_width=0, sensorSize_height=0, camerMatrix=Matrix{rows=3, cols=3, dt=d, data=[662.7859988712237, 0.0, 312.4425601600666, 0.0, 661.2927687519908, 227.4717976712425, 0.0, 0.0, 1.0]}, distCoeffs=Matrix{rows=5, cols=1, dt=d, data=[-0.1884833834146469, 1.0721890419183855, -0.0035244467228016116, -7.01950328482414E-4, -2.04128279990271]}}
---
imageSize_width: 640
imageSize_height: 480
sensorSize_width: 0
sensorSize_height: 0
reprojectionError: 0.21723265945911407
cameraMatrix:
  rows: 3
  cols: 3
  dt: "d"
  data:
 - 662.7859988712237
 - 0.0
 - 312.4425601600666
 - 0.0
 - 661.2927687519908
 - 227.4717976712425
 - 0.0
 - 0.0
 - 1.0
distCoeffs:
  rows: 5
  cols: 1
  dt: "d"
  data:
 - -0.1884833834146469
 - 1.0721890419183855
 - -0.0035244467228016116
 - -7.01950328482414E-4
 - -2.04128279990271

请建议将解决方案与 org.opencv.core.Mat


问题答案:

你看过杰克逊图书馆吗?它允许将JSON / Yaml内容映射到Java POJO。

我举了一个小例子来解决您的两个问题:

  • 用MatYAML字符串交换opencv-matrix
  • 手动转换HashMap值

但是,对于yaml版本指令,由于它似乎无效,因此我不确定如何处理它。在我的示例中,我已预先手动将其删除。当然,可以找到更好的解决方案,但我不知道。

EDIT2 :对于矩阵对象,我做了一个笨拙的POJO,供Jackson用来内部读取brut YAML。然后,我添加了一个转换层(见
@JsonSerialize@JsonDeserialize 上anotations OpenCVConfig
类)这个简单的POJO转换为专业OpenCV的矩阵。Jackson提供了多种映射技术(流技术,自定义转换器/解串器,指导注释等),因此您可以探索其功能,以找到最适合您需要的解决方案

因此,要使该示例正常工作,您将需要两个依赖项(以maven格式给出):

        <!-- (De)serialization engine -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.10.0</version>
        </dependency>

        <!-- Yaml support -->
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-yaml</artifactId>
            <version>2.10.0</version>
        </dependency>

这是Maven存储库描述页面:

Jackson-databind
:通过Maven搜索或通过mvnrepository

Jackson-dataformat-yaml
:通过Maven搜索或通过mvnrepository

注意 :我的示例包含很多样板,就像我手动(以及IDE)生成的getter / setter一样。您应该通过以下两种方式减少代码量:

  • 使用Lombok lib
  • 用Kotlin编码(数据类)
  • 在没有getter / setter的情况下使用公共属性
  • 使用Java 14记录(不确定当前是否可用)

    package fr.amanin.stackoverflow;

    import java.util.Arrays;
    import java.util.stream.Stream;

    import com.fasterxml.jackson.core.type.TypeReference;
    import com.fasterxml.jackson.databind.JavaType;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
    import com.fasterxml.jackson.databind.annotation.JsonSerialize;
    import com.fasterxml.jackson.databind.type.TypeFactory;
    import com.fasterxml.jackson.databind.util.Converter;
    import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
    import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
    import org.opencv.core.CvType;
    import org.opencv.core.Mat;

    public class YAMLOpenCV {
    /*
    * Engine in charge of YAML decoding.
    /
    public static final ObjectMapper OPENCV_YAML_MAPPER = new YAMLMapper();

    public static void main(String[] args) throws Exception {
        nu.pattern.OpenCV.loadShared();
        System.loadLibrary(org.opencv.core.Core.NATIVE_LIBRARY_NAME);
        final String confStr =
                "cameraMatrix: !!opencv-matrix\n" +
                        "   rows: 3\n" +
                        "   cols: 3\n" +
                        "   dt: d\n" +
                        "   data: [ 6.6278599887122368e+02, 0., 3.1244256016006659e+02, 0.,\n" +
                        "       6.6129276875199082e+02, 2.2747179767124251e+02, 0., 0., 1. ]\n" +
                        "imageSize_width: 640\n" +
                        "imageSize_height: 480\n" +
                        "sensorSize_width: 0\n" +
                        "sensorSize_height: 0\n" +
                        "distCoeffs: !!opencv-matrix\n" +
                        "   rows: 5\n" +
                        "   cols: 1\n" +
                        "   dt: d\n" +
                        "   data: [ -1.8848338341464690e-01, 1.0721890419183855e+00,\n" +
                        "       -3.5244467228016116e-03, -7.0195032848241403e-04,\n" +
                        "       -2.0412827999027101e+00 ]\n" +
                        "reprojectionError: 2.1723265945911407e-01";
    
        OpenCVConfig conf = OPENCV_YAML_MAPPER.readValue(confStr, OpenCVConfig.class);
        System.out.println(conf);
    
        String serialized = OPENCV_YAML_MAPPER.writeValueAsString(conf);
        System.out.println(serialized);
    }
    
    /**
     * Java model mirroring YAML configuration. Jackson will fill it 
     * with values read from YAML configuration file, matching YAML 
     * fields with this class property names.
     */
    public static class OpenCVConfig {
        int imageSize_width;
        int imageSize_height;
        int sensorSize_width;
        int sensorSize_height;
    
        double reprojectionError;
    
        /* Special case: Matrix objects are decoded in two passes:
         * 1. Jackson will check below converters, and use their input 
         * type to decode YAML to `Matrix` object (intermediate step). 
         * 2. Jackson uses converter to delegate to user the mapping 
         * from this intermediate POJO to specialized target (`Mat` here) 
         */
        @JsonDeserialize(converter = ToMatConverter.class)
        @JsonSerialize(converter = FromMatConverter.class)
        Mat cameraMatrix;
        @JsonDeserialize(converter = ToMatConverter.class)
        @JsonSerialize(converter = FromMatConverter.class)
        Mat distCoeffs;
    
        public int getImageSize_width() {
            return imageSize_width;
        }
    
        public OpenCVConfig setImageSize_width(int imageSize_width) {
            this.imageSize_width = imageSize_width;
            return this;
        }
    
        public int getImageSize_height() {
            return imageSize_height;
        }
    
        public OpenCVConfig setImageSize_height(int imageSize_height) {
            this.imageSize_height = imageSize_height;
            return this;
        }
    
        public int getSensorSize_width() {
            return sensorSize_width;
        }
    
        public OpenCVConfig setSensorSize_width(int sensorSize_width) {
            this.sensorSize_width = sensorSize_width;
            return this;
        }
    
        public int getSensorSize_height() {
            return sensorSize_height;
        }
    
        public OpenCVConfig setSensorSize_height(int sensorSize_height) {
            this.sensorSize_height = sensorSize_height;
            return this;
        }
    
        public double getReprojectionError() {
            return reprojectionError;
        }
    
        public OpenCVConfig setReprojectionError(double reprojectionError) {
            this.reprojectionError = reprojectionError;
            return this;
        }
    
        public Mat getCameraMatrix() {
            return cameraMatrix;
        }
    
        public OpenCVConfig setCameraMatrix(Mat cameraMatrix) {
            this.cameraMatrix = cameraMatrix;
            return this;
        }
    
        public Mat getDistCoeffs() {
            return distCoeffs;
        }
    
        public OpenCVConfig setDistCoeffs(Mat distCoeffs) {
            this.distCoeffs = distCoeffs;
            return this;
        }
    
        @Override
        public String toString() {
            return "OpenCVConfig{" +
                    "imageSize_width=" + imageSize_width +
                    ", imageSize_height=" + imageSize_height +
                    ", sensorSize_width=" + sensorSize_width +
                    ", sensorSize_height=" + sensorSize_height +
                    ", camerMatrix=" + cameraMatrix +
                    ", distCoeffs=" + distCoeffs +
                    '}';
        }
    }
    
    /**
     * Converter used for serialization of Mat objects into YAML.
     */
    private static class FromMatConverter implements Converter<Mat, Matrix> {
    
        @Override
        public Matrix convert(Mat value) {
            final Matrix result = new Matrix();
            result.cols = value.cols();
            result.rows = value.rows();
            final int type = value.type();
            result.dt = Stream.of(MatrixDataType.values())
                    .filter(dt -> dt.mapping == type)
                    .findAny()
                    .orElseThrow(() -> new UnsupportedOperationException("No matching datatype found for "+type));
            int idx = 0;
            result.data = new double[result.rows * result.cols];
            for (int r = 0 ; r < result.rows ; r++) {
                for (int c = 0; c < result.cols; c++) {
                    final double[] v = value.get(r, c);
                    result.data[idx++] = v[0];
                }
            }
            return result;
        }
    
        @Override
        public JavaType getInputType(TypeFactory typeFactory) {
            return typeFactory.constructType(new TypeReference<Mat>() {});
        }
    
        @Override
        public JavaType getOutputType(TypeFactory typeFactory) {
            return typeFactory.constructType(new TypeReference<Matrix>() {});
        }
    }
    
    /**
     * Converter used at read time, to map YAML object to OpenCV Mat.
     */
    private static class ToMatConverter implements Converter<Matrix, Mat> {
    
        @Override
        public Mat convert(Matrix in) {
            final Mat result = new Mat(in.rows, in.cols, in.dt.mapping);
    
            int idx = 0;
            for (int r = 0 ; r < in.rows ; r++) {
                for (int c = 0; c < in.cols; c++) {
                    result.put(r, c, in.data[idx++]);
                }
            }
    
            return result;
        }
    
        @Override
        public JavaType getInputType(TypeFactory typeFactory) {
            return typeFactory.constructType(new TypeReference<Matrix>() {});
        }
    
        @Override
        public JavaType getOutputType(TypeFactory typeFactory) {
            return typeFactory.constructType(new TypeReference<Mat>() {});
        }
    }
    
    public static class Matrix {
        int rows;
        int cols;
        MatrixDataType dt;
        double[] data;
    
        public int getRows() {
            return rows;
        }
    
        public Matrix setRows(int rows) {
            this.rows = rows;
            return this;
        }
    
        public int getCols() {
            return cols;
        }
    
        public Matrix setCols(int cols) {
            this.cols = cols;
            return this;
        }
    
        public MatrixDataType getDt() {
            return dt;
        }
    
        public Matrix setDt(MatrixDataType dt) {
            this.dt = dt;
            return this;
        }
    
        public double[] getData() {
            return data;
        }
    
        public Matrix setData(double[] data) {
            this.data = data;
            return this;
        }
    
        double at(int x, int y) {
            if (x >= cols || y >= rows) throw new IllegalArgumentException("Bad coordinate");
            return data[y*rows + x];
        }
    
        @Override
        public String toString() {
            return "Matrix{" +
                    "rows=" + rows +
                    ", cols=" + cols +
                    ", dt=" + dt +
                    ", data=" + Arrays.toString(data) +
                    '}';
        }
    }
    

    /*
    public static class MatDeserializer extends StdDeserializer {

        protected MatDeserializer() {
            super(Mat.class);
        }
    
        @Override
        public Mat deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
            final int rows, cols;
            final MatrixDataType dtype;
            final double[] data;
        }
    }
    
    public static class MatSerializer extends StdSerializer<Mat> {
    
        protected MatSerializer() {
            super(Mat.class);
        }
    
        @Override
        public void serialize(Mat value, JsonGenerator gen, SerializerProvider provider) throws IOException {
            gen.writeNumberField("rows", value.rows());
            gen.writeNumberField("cols", value.cols());
            gen.writeFieldName("data");
            gen.writeStartArray();
            gen.writeEndArray();
        }
    }
    

    */
    public enum MatrixDataType {
    d(CvType.CV_64F),
    f(CvType.CV_32F);

        public final int mapping;
        MatrixDataType(int mapping) {
            this.mapping = mapping;
        }
    }
    

    }

希望能帮助到你,



 类似资料:
  • 我有一个kdtree,其节点由以下字段组成:公共静态类节点实现可序列化{ 其中DataPoint定义: 公共静态类DataPoint实现可序列化{公共可比X;公共可比Y;公共可比Z; 我想序列化树,存储在文件中并在回答范围查询时反序列化。我对这个概念od序列化的理解并不好。从我收集的任何内容中,我编写了以下函数,但不起作用。有人能帮忙吗?

  • 问题内容: 我的直觉告诉我,必须以某种方式将其转换为字符串或byte [](在Go中甚至可能是相同的东西?),然后将其保存到磁盘。 我找到了这个包(http://golang.org/pkg/encoding/gob/),但似乎仅用于结构? 问题答案: 序列化数据有多种方法,Go为此提供了许多软件包。某些常见编码方式的软件包: 处理地图很好。以下示例显示了地图的编码/解码: 操场

  • 主要内容:1 Java序列化和反序列化,2 Java序列化的优点,3 java.io.Serializable接口,4 Java ObjectOutputStream,5 Java ObjectInputStream,6 Java序列化的例子,7 Java反序列化的例子1 Java序列化和反序列化 Java中的序列化是一种将对象状态写入字节流的机制。它主要用于Hibernate,RMI,JPA,EJB和JMS技术。 序列化的反向操作称为反序列化,其中字节流被转换为对象。序列化和反序列化过程与平台

  • 问题内容: 我已经开始在我的第一个android应用程序上进行工作,并且具有处理多层图像的应用程序。我能够将项目文件的平面版本导出为PNG,但我希望能够保存分层图像以供以后编辑(包括应用于某些层的任何选项,例如基于文本的层)。 无论如何,我已经确保需要写入文件的类是“可序列化的”,但是由于android.graphics.Bitmap不可序列化这一事实而遇到了一些障碍。以下代码实质上将位图作为PN

  • 我有两个Java应用程序-和。需要通过套接字发送到类的Slave实例。 Master创建这个类的实例,序列化它并通过套接字发送到Slave。 在上,一切正常。没有异常。接收数据并尝试对其进行反序列化。引发以下异常 JAVAlang.ClassNotFoundException 在类中没有错误,因为如果我用优化函数=null创建它,那么就会毫无问题地反序列化它。我试图将实例序列化到中的文件中,然后也

  • 如题目所述,想请教一下大佬们在Java中序列化与反序列化的意义是什么,如何理解Java的序列化和反序列化?