如何使用 XML store/load 内部相机参数

How to store/load IntrinsicCameraParameters with XML

我假设使用 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);                
        }

.Net 包含一个 class XmlSerializer that can automatically serialize instances of types from and to XML via reflection of public properties. It also supports the IXmlSerializable 接口,允许类型覆盖其默认行为并完全控制它们如何序列化为 XML.

事实证明,IntrinsicCameraParameters.IntrinsicMatrix and IntrinsicCameraParameters.DistortionCoeffs are both of type Matrix<double> whose base class 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);

为了确认,请参阅 the documentation 的另一个序列化矩阵的示例 XML。

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

ICP.SerializeToXmlFile(fileName);

很遗憾,出现了问题。 ReadXml()CvArray<TDepth> 的实现被破坏了。来自 Proper way to implement IXmlSerializable?

The ReadXml method must reconstitute your object using the information that was written by the WriteXml method.

When this method is called, the reader is positioned at the start of the element that wraps the information for your type. That is, just before the start tag that indicates the beginning of a serialized object. When this method returns, it must have read the entire element from beginning to end, including all of its contents. Unlike the WriteXml method, the framework does not handle the wrapper element automatically. Your implementation must do so. Failing to observe these positioning rules may cause code to generate unexpected runtime exceptions or corrupt data.

快速检查 CvArray<TDepth>source code 表明包装器元素的末尾 未读取 。这将导致 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 在当前版本中是 marked as obsolete

[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
    }