.Net contains a class XmlSerializer
that can automatically serialize instances of types from and to XML via reflection of public properties. It also supports the IXmlSerializable
interface to allow types to override its default behavior and take complete control of how they are serialized to XML.
As it turns out, IntrinsicCameraParameters.IntrinsicMatrix
and IntrinsicCameraParameters.DistortionCoeffs
are both of type Matrix<double>
whose base class CvArray<TDepth>
implements this interface. Thus objects of this type should be serializeable to XML using XmlSerializer
using the following extension methods:
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);
}
}
}
Then you can just do:
var fileName = "C:\\Video\\Cal\\CameraMatrix.xml";
ICP.IntrinsicMatrix.SerializeToXmlFile(fileName);
And later, to read it back, do:
var newIntrinsicMatrix = XmlSerializationExtensions.DeserializeFromXmlFile<Matrix<double>>(fileName);
For confirmation, see the documentation for another example of serializing a matrix from and to XML.
Logically, it should also be possible to save and restore the entire IntrinsicCameraParameters
in a single XML file using the same generic extension method:
ICP.SerializeToXmlFile(fileName);
Unfortunately, there's a problem. The implementation of ReadXml()
for CvArray<TDepth>
is broken. From 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.
And a quick check of the source code for CvArray<TDepth>
shows that the end of the wrapper element is not read. This will cause any data subsequent to the first CvArray<>
in the XML to fail to be deserialized.
Thus, if you want to embed a Matrix<T>
in a larger XML file, you're going to need to introduce serialization surrogate types (or data transfer object types if you prefer), like the following. (Note the introduction of the Emgu.CV.SerializationSurrogates
namespace):
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,
};
}
}
}
Now you can save and retrieve your IntrinsicCameraParameters
with the following extension methods:
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;
}
}
All that being said, IntrinsicCameraParameters
is marked as obsolete in the current release:
[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>
So you might want to re-think this design.
Incidentally, a non-broken version of CvArray<TDepth>.ReadXml()
would look like:
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
}