如何用XML存储/加载IntrinsicCameraParameters

本文关键字:加载 IntrinsicCameraParameters 存储 何用 XML | 更新日期: 2023-09-27 18:05:07

我假设使用EmguCV库执行的相机校准是成功的,因为我可以将校准应用于图像,并且它们似乎至少在一定程度上得到了纠正。现在我想将校准存储到磁盘上,以便在程序运行时加载它。IntrinsicCameraParameters有两个方法writeXML()和readXML(),它们分别接受XmlWriter和XmlReader作为参数。似乎是该走的路。我发现了一个示例,其中某人刚刚实例化了一个默认XmlWriter并立即使用它来调用WriteXml()。但是当我尝试这样做时,我得到的运行时异常似乎与XML的结构有关(即只能在XML中有一个根节点)。所以我调整了代码并在下面分享。如果我不包含愚蠢的根元素,那么对"WriteXml"的调用就会抛出关于不正确格式的XML的异常。所以我似乎可以把它写下来,但我不知道如何读回去。也许是愚蠢的根元素阻止了读取的成功。找不到任何有人读过的例子。有谁能举个例子吗?

public void SaveCalibrationToFile(IntrinsicCameraParameters ICP)
        {
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.ConformanceLevel = ConformanceLevel.Auto;
            // DISTORTION COEFFICIENTS
            XmlWriter writer = XmlWriter.Create("C:''Video''Cal''DistortionCoeff.xml", settings);               
            writer.WriteStartDocument();
            writer.WriteStartElement("TheStupidRootElement");
                writer.WriteStartElement("TheDistortionCoefficients");
                ICP.DistortionCoeffs.WriteXml(writer);
                writer.WriteEndElement(); // end TheDistortionCoefficients
            writer.WriteEndElement(); // end TheStupidRootElement
            writer.WriteEndDocument();
            writer.Close();
            // CAMERA MATRIX
            writer = XmlWriter.Create("C:''Video''Cal''CameraMatrix.xml", settings);                
            writer.WriteStartDocument();
            writer.WriteStartElement("TheStupidRootElement");
                writer.WriteStartElement("TheCameraMatrix");
                ICP.IntrinsicMatrix.WriteXml(writer);
                writer.WriteEndElement(); // end TheCameraMatrix
            writer.WriteEndElement(); // end TheStupidRootElement
            writer.WriteEndDocument();
            writer.Close();
            // now [just to see if it worked] try to load from the XML
            XmlReaderSettings readerSettings = new XmlReaderSettings();
            readerSettings.ConformanceLevel = ConformanceLevel.Auto;
            XmlReader reader = XmlReader.Create("C:''Video''Cal''DistortionCoeff.xml", readerSettings);
            IntrinsicCameraParameters ICP_read = new IntrinsicCameraParameters();
            IC.DistortionCoeffs.ReadXml(reader);                
        }

如何用XML存储/加载IntrinsicCameraParameters

。Net包含一个类XmlSerializer,它可以通过公共属性的反射自动序列化来自XML和到XML的类型实例。它还支持IXmlSerializable接口,允许类型覆盖其默认行为,并完全控制如何将它们序列化为XML。

结果表明,IntrinsicCameraParameters.IntrinsicMatrixIntrinsicCameraParameters.DistortionCoeffs都是Matrix<double>类型,其基类CvArray<TDepth>实现了该接口。因此,这种类型的对象应该使用XmlSerializer使用以下扩展方法序列化为XML:

public static partial class XmlSerializationExtensions
{
    public static void SerializeToXmlFile<T>(this T obj, string fileName)
    {
        var settings = new XmlWriterSettings { Indent = true };
        using (var writer = XmlWriter.Create(fileName, settings))
        {
            new XmlSerializer(typeof(T)).Serialize(writer, obj);
        }
    }
    public static T DeserializeFromXmlFile<T>(string fileName)
    {
        using (var reader = XmlReader.Create(fileName))
        {
            return (T)new XmlSerializer(typeof(T)).Deserialize(reader);
        }
    }
}

然后你可以这样做:

var fileName = "C:''Video''Cal''CameraMatrix.xml";
ICP.IntrinsicMatrix.SerializeToXmlFile(fileName);

然后,读取它,执行:

var newIntrinsicMatrix = XmlSerializationExtensions.DeserializeFromXmlFile<Matrix<double>>(fileName);

要确认,请参阅文档中关于从XML到XML序列化矩阵的另一个示例。

从逻辑上讲,也应该可以使用相同的通用扩展方法在单个XML文件中保存和恢复整个IntrinsicCameraParameters:
ICP.SerializeToXmlFile(fileName);

不幸的是,有一个问题。ReadXml()CvArray<TDepth>的实现被破坏了。从实现IXmlSerializable的正确方法?

ReadXml方法必须重构您的对象使用的信息是是由WriteXml方法编写的。

当这个方法被调用时,读取器的开始位置元素,该元素为你的类型。也就是说,就在表示开始的起始标记序列化对象的。当这个方法返回时,它必须读取整个元素从头到尾,包括它的所有内容。不像WriteXml方法,框架不处理包装器元素自动。您的实现必须这么做。没有注意到这些定位规则可能导致代码生成意外的运行时异常或者数据损坏

CvArray<TDepth>的源代码的快速检查显示,包装器元素的结尾是而不是读取。这将导致XML中第一个CvArray<>之后的任何数据都无法反序列化。

因此,如果希望在更大的XML文件中嵌入Matrix<T>,则需要引入序列化代理类型(如果愿意,也可以是数据传输对象类型),如下所示。(注意Emgu.CV.SerializationSurrogates名称空间的引入):

namespace Emgu.CV.SerializationSurrogates
{
    using Emgu.CV;
    public class Matix<TDepth> where TDepth : new()
    {
        [XmlAttribute]
        public int Rows { get; set; }
        [XmlAttribute]
        public int Cols { get; set; }
        [XmlAttribute]
        public int NumberOfChannels { get; set; }
        [XmlAttribute]
        public int CompressionRatio { get; set; }
        public byte[] Bytes { get; set; }
        public static implicit operator Emgu.CV.SerializationSurrogates.Matix<TDepth>(Emgu.CV.Matrix<TDepth> matrix)
        {
            if (matrix == null)
                return null;
            return new Matix<TDepth>
            {
                Rows = matrix.Rows,
                Cols = matrix.Cols,
                NumberOfChannels = matrix.NumberOfChannels,
                CompressionRatio = matrix.SerializationCompressionRatio,
                Bytes = matrix.Bytes,
            };
        }
        public static implicit operator Emgu.CV.Matrix<TDepth>(Matix<TDepth> surrogate)
        {
            if (surrogate == null)
                return null;
            var matrix = new Emgu.CV.Matrix<TDepth>(surrogate.Rows, surrogate.Cols, surrogate.NumberOfChannels);
            matrix.SerializationCompressionRatio = surrogate.CompressionRatio;
            matrix.Bytes = surrogate.Bytes;
            return matrix;
        }
    }
    public class IntrinsicCameraParameters
    {
        [XmlElement("IntrinsicMatrix", Type = typeof(Emgu.CV.SerializationSurrogates.Matix<double>))]
        public Emgu.CV.Matrix<double> IntrinsicMatrix { get; set; }
        [XmlElement("DistortionCoeffs", Type = typeof(Emgu.CV.SerializationSurrogates.Matix<double>))]
        public Emgu.CV.Matrix<double> DistortionCoeffs { get; set; }
        public static implicit operator Emgu.CV.SerializationSurrogates.IntrinsicCameraParameters(Emgu.CV.IntrinsicCameraParameters icp)
        {
            if (icp == null)
                return null;
            return new IntrinsicCameraParameters
            {
                DistortionCoeffs = icp.DistortionCoeffs,
                IntrinsicMatrix = icp.IntrinsicMatrix,
            };
        }
        public static implicit operator Emgu.CV.IntrinsicCameraParameters(Emgu.CV.SerializationSurrogates.IntrinsicCameraParameters surrogate)
        {
            if (surrogate == null)
                return null;
            return new Emgu.CV.IntrinsicCameraParameters
            {
                DistortionCoeffs = surrogate.DistortionCoeffs,
                IntrinsicMatrix = surrogate.IntrinsicMatrix,
            };
        }
    }
}

现在您可以保存和检索您的IntrinsicCameraParameters与以下扩展方法:

public static class IntrinsicCameraParametersExtensions
{
    public static void SerializeIntrinsicCameraParametersExtensionsToXmlFile(this IntrinsicCameraParameters icp, string fileName)
    {
        var surrogate = (Emgu.CV.SerializationSurrogates.IntrinsicCameraParameters)icp;
        surrogate.SerializeToXmlFile(fileName);
    }
    public static IntrinsicCameraParameters DeserializeIntrinsicCameraParametersFromXmlFile(string fileName)
    {
        var surrogate = XmlSerializationExtensions.DeserializeFromXmlFile<Emgu.CV.SerializationSurrogates.IntrinsicCameraParameters>(fileName);
        return surrogate;
    }
}

话虽如此,IntrinsicCameraParameters在当前版本中被标记为过时的:

[SerializableAttribute]
[ObsoleteAttribute("This class will be removed in the next release, please use separate camera matrix and distortion coefficient with the CvInvoke function instead.")]
public class IntrinsicCameraParameters : IEquatable<IntrinsicCameraParameters>

所以你可能需要重新考虑这个设计。

顺便说一下,CvArray<TDepth>.ReadXml()的完整版本看起来像:
    public virtual void ReadXml(System.Xml.XmlReader reader)
    {
        #region read properties of the matrix and assign storage
        int rows = Int32.Parse(reader.GetAttribute("Rows")); // Should really be using XmlConvert for this
        int cols = Int32.Parse(reader.GetAttribute("Cols"));
        int numberOfChannels = Int32.Parse(reader.GetAttribute("NumberOfChannels"));
        SerializationCompressionRatio = Int32.Parse(reader.GetAttribute("CompressionRatio"));
        AllocateData(rows, cols, numberOfChannels);
        #endregion
        #region decode the data from Xml and assign the value to the matrix
        if (!reader.IsEmptyElement)
        {
            using (var subReader = reader.ReadSubtree())
            {
                // Using ReadSubtree guarantees we don't read past the end of the element in case the <Bytes> element
                // is missing or extra unexpected elements are included.
                if (subReader.ReadToFollowing("Bytes"))
                {
                    int size = _sizeOfElement * ManagedArray.Length;
                    if (SerializationCompressionRatio == 0)
                    {
                        Byte[] bytes = new Byte[size];
                        subReader.ReadElementContentAsBase64(bytes, 0, bytes.Length);
                        Bytes = bytes;
                    }
                    else
                    {
                        int extraHeaderBytes = 20000;
                        Byte[] bytes = new Byte[size + extraHeaderBytes];
                        int countOfBytesRead = subReader.ReadElementContentAsBase64(bytes, 0, bytes.Length);
                        Array.Resize<Byte>(ref bytes, countOfBytesRead);
                        Bytes = bytes;
                    }
                }
            }
        }
        // Consume the end of the wrapper element also.
        reader.Read();
        #endregion
    }