Don't use the memory leak operator:
X a = *new X(); // guaranteed unrecoverable memory leak¹
The place where you used it is riddled with other problems:
flowX = *new string((const char*)matX.col(0).data);
flowY = *new string((const char*)matY.col(0).data);
Firstly, (const char*)data is a reinterpret-cast. It's rarely safe to use.
flowX = std::string(reinterpret_cast<const char*>(matX.col(0).data), length);
flowY = std::string(reinterpret_cast<const char*>(matY.col(0).data), length);
You interpret that as a raw, C-style string. C strings need to be NUL-terminated. There is no reason to assume they are. Specify a length.
flowX = std::string(reinterpret_cast<const char*>(matX.col(0).data), length);
flowY = std::string(reinterpret_cast<const char*>(matY.col(0).data), length);
Or indeed, use flowX.assign
flowX.assign(reinterpret_cast<const char*>(matX.col(0).data), length);
flowY.assign(reinterpret_cast<const char*>(matY.col(0).data), length);
This line
cv::Mat matX = Mat(FlowPlanes[0]);
needlessly copy-constructs
cv::Nat matX(FlowPlanes[0]);
There seems to be no reason to copy the matrices anyway
void PrepData() {
flowX.assign(reinterpret_cast<const char*>(FlowPlanes[0].col(0).data), length);
flowY.assign(reinterpret_cast<const char*>(FlowPlanes[1].col(0).data), length);
}
In fact, since nothing else ever uses flowPlanes, just initialize in the constructor, as usual:
std::string flowX;
std::string flowY;
SerieFlow() = default;
explicit SerieFlow(cv::cuda::GpuMat* flowPlanes)
: flowX(reinterpret_cast<const char*>(flowPlanes[0].col(0).data), length),
flowY(reinterpret_cast<const char*>(flowPlanes[1].col(0).data), length)
{ }
Many other conversion confusions can be simplified:
unsigned char data[8] = { 7, 4, 2, 6, 7, 18, 29, 111 };
std::string dataAsString(reinterpret_cast<const char*>(data), 8);
To
std::string dataAsString { 7, 4, 2, 6, 7, 18, 29, 111 };
And
std::string dataAsString { 7, 4, 2, 6, 7, 18, 29, 111 };
std::vector<unsigned char> expectedData{ dataAsString.begin(),
dataAsString.end() };
To simply...
std::vector<unsigned char> data { 7, 4, 2, 6, 7, 18, 29, 111 };
cv::Mat mat(data.size(), 1, CV_8UC1, data.data());
No need for temporaries to create the array:
cv::cuda::GpuMat gpuMat1(mat);
cv::cuda::GpuMat gpuMat2(mat);
cv::cuda::GpuMat gpuMatArray[2] = { gpuMat1, gpuMat2 };
Can be
GpuMat gpuMatArray[2] = { GpuMat{mat}, GpuMat{mat} };
In general the whole conversion to std::string is unnecessary and what
creates the incompatibilities with uchar vs char. Just keep it vector<uchar>?
using cv::cuda::GpuMat;
using uchar = std::uint8_t;
using TwoMat = std::array<GpuMat, 2>;
class SerieFlow {
friend class boost::serialization::access;
template <class Archive> void serialize(Archive& ar, unsigned /*unused*/) {
ar& flowX & flowY;
}
static auto to_vector(GpuMat const& mat) {
assert(mat.elemSize() == 1);
auto n = mat.size().area() * mat.elemSize();
return std::vector<uchar>(mat.data, mat.data+n);
}
public:
std::vector<uchar> flowX, flowY;
SerieFlow() = default;
explicit SerieFlow(TwoMat const planes)
: flowX(to_vector(planes[0])),
flowY(to_vector(planes[1])) {}
};
struct SerieFlowFile {
static void Save(const SerieFlow& content, const std::string& filename) {
std::ofstream stream(filename);
boost::archive::text_oarchive archive(stream);
archive << content;
}
static SerieFlow Open(const std::string& filename) {
std::ifstream stream(filename);
boost::archive::text_iarchive archive(stream);
SerieFlow content;
archive >> content;
return content;
}
};
void ReadDeserializeTest() {
std::filesystem::path filename = "WriteRead.sflow";
if (exists(filename)) {
remove(filename);
}
std::vector<unsigned char> data { 7, 4, 2, 6, 7, 18, 29, 111 };
cv::Mat mat(data.size(), 1, CV_8UC1, data.data());
TwoMat gpuMatArray { GpuMat{mat}, GpuMat{mat} };
SerieFlow sflow(gpuMatArray);
SerieFlowFile::Save(sflow, filename);
auto roundtrip = SerieFlowFile::Open(filename);
auto check = [&data](auto& flowX, auto label) {
std::cout << label << " size: " << flowX.size();
copy(flowX.begin(), flowX.end(), std::ostream_iterator<int>(std::cout, " "));
std::cout << "\n" << (data == flowX? "CORRECT":"INCORRECT") << std::endl;
};
check(roundtrip.flowX, "flowX");
check(roundtrip.flowY, "flowY");
}
int main() {
ReadDeserializeTest();
}
Out Of The Box: Just Serialize GpuMat
Why don't you add serialization for the GpuMat type directly?
#include <boost/serialization/array.hpp>
#include <boost/serialization/array_wrapper.hpp>
namespace boost::serialization {
template <typename Ar> void save(Ar& ar, GpuMat const& mat, unsigned) {
int r = mat.rows;
int c = mat.cols;
ar & r & c & make_array(&mat.data[0], r*c);
}
template <typename Ar> void load(Ar& ar, GpuMat& mat, unsigned) {
int r, c;
ar & r & c;
mat = GpuMat(r, c, CV_8UC1);
ar & make_array(&mat.data[0], r*c);
}
}
BOOST_SERIALIZATION_SPLIT_FREE(GpuMat)
This removes all the copying and should immediatly fix the issue of not dealing
with the actual runtime shape of the matrix (you just assumed it will always be
1 row).
Now the whole thing can be implemented as just
using Flows = std::array<GpuMat, 2>;
struct SerieFlowFile {
static void Save(const Flows& content, const std::string& filename) {
std::ofstream stream(filename);
boost::archive::text_oarchive archive(stream);
archive << content;
}
static Flows Open(const std::string& filename) {
std::ifstream stream(filename);
boost::archive::text_iarchive archive(stream);
Flows content;
archive >> content;
return content;
}
};
Which compiles with the following test main:
void ReadDeserializeTest() {
std::vector<uchar> data { 7, 4, 2, 6, 7, 18, 29, 111 };
Flows gpuMatArray {
GpuMat (8, 1, CV_8UC1, data.data()),
GpuMat (4, 1, CV_8UC1, data.data()),
};
SerieFlowFile::Save(gpuMatArray, "WriteRead.sflow");
auto roundtrip = SerieFlowFile::Open("WriteRead.sflow");
static auto as_vec = [](GpuMat const& mat) {
return std::vector(&mat.data[0], &mat.data[mat.cols * mat.rows]);
};
auto check = [&] (int index) {
auto const& v = as_vec(roundtrip[index]);
auto eq = boost::equal(as_vec(gpuMatArray[index]), v);
fmt::print("#{} size: {} {} {}\n", index, v.size(), v, (eq? "CORRECT":"INCORRECT"));
};
check(0);
check(1);
}
See it Live On Compiler Explorer
//#pragma once
#include <array>
#include <cstdint>
using uchar = std::uint8_t;
#ifndef NO_OPENCV
#include <opencv2/core/cuda.hpp>
//using cv::cuda::GpuMat;
using GpuMat = cv::Mat;
#else
#include <memory>
#include <algorithm>
namespace {
enum {CV_8UC1};
struct FakeMat {
FakeMat(int r=1, int c=1, int=CV_8UC1, void* init = nullptr)
: rows(r), cols(c), data(r && c? std::make_unique<uchar[]>(r*c) : nullptr)
{
if (init && data) {
std::copy_n(static_cast<uchar const*>(init), rows*cols, data.get());
}
}
FakeMat(FakeMat const& rhs)
: rows(rhs.rows), cols(rhs.cols), data(rhs.data? std::make_unique<uchar[]>(rows*cols) : nullptr)
{
if (data && rhs.data) {
std::copy_n(rhs.data.get(), rows*cols, data.get());
}
}
FakeMat(FakeMat&&) = default;
FakeMat& operator=(FakeMat&&) = default;
int rows, cols;
std::unique_ptr<uchar[]> data;
};
}
using GpuMat = FakeMat;
#endif
#include <boost/serialization/array.hpp>
#include <boost/serialization/array_wrapper.hpp>
namespace boost::serialization {
template <typename Ar> void save(Ar& ar, GpuMat const& mat, unsigned) {
int r = mat.rows;
int c = mat.cols;
ar & r & c & make_array(&mat.data[0], r*c);
}
template <typename Ar> void load(Ar& ar, GpuMat& mat, unsigned) {
int r, c;
ar & r & c;
mat = GpuMat(r, c, CV_8UC1);
ar & make_array(&mat.data[0], r*c);
}
}
BOOST_SERIALIZATION_SPLIT_FREE(GpuMat)
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <fstream>
using Flows = std::array<GpuMat, 2>;
struct SerieFlowFile {
static void Save(const Flows& content, const std::string& filename) {
std::ofstream stream(filename);
boost::archive::text_oarchive archive(stream);
archive << content;
}
static Flows Open(const std::string& filename) {
std::ifstream stream(filename);
boost::archive::text_iarchive archive(stream);
Flows content;
archive >> content;
return content;
}
};
#include <boost/range.hpp>
#include <fmt/ranges.h>
void ReadDeserializeTest() {
std::vector<uchar> data { 7, 4, 2, 6, 7, 18, 29, 111 };
Flows gpuMatArray {
GpuMat (8, 1, CV_8UC1, data.data()),
GpuMat (4, 1, CV_8UC1, data.data()+2),
};
SerieFlowFile::Save(gpuMatArray, "WriteRead.sflow");
auto roundtrip = SerieFlowFile::Open("WriteRead.sflow");
static auto as_vec = [](GpuMat const& mat) {
return std::vector(&mat.data[0], &mat.data[mat.cols * mat.rows]);
};
auto check = [&] (int index) {
auto const& v = as_vec(roundtrip[index]);
auto eq = boost::equal(as_vec(gpuMatArray[index]), v);
fmt::print("#{} size: {} {} {}\n", index, v.size(), v, (eq? "CORRECT":"INCORRECT"));
};
check(0);
check(1);
}
int main() {
ReadDeserializeTest();
}
Prints
#0 size: 8 {7, 4, 2, 6, 7, 18, 29, 111} CORRECT
#1 size: 4 {2, 6, 7, 18} CORRECT