More accurate tracking of external memory using OpenCV’s reference counts

This commit is contained in:
David Starke 2018-01-04 20:16:36 -08:00
parent dff99718b9
commit f8d7dc2f06
6 changed files with 1126 additions and 119 deletions

View File

@ -283,7 +283,7 @@ NAN_METHOD(BackgroundSubtractorWrap::ApplyMOG) {
#endif
}
Local<Object> fgMask = Matrix::CreateWrappedFromMat(_fgMask);
Local<Object> fgMask = Matrix::CreateWrappedFromMat(_fgMask.clone());
mat.release();
argv[0] = Nan::Null();
@ -309,15 +309,15 @@ public:
AsyncBackgroundSubtractorWorker(
Nan::Callback *callback,
BackgroundSubtractorWrap *bg,
cv::Mat &img_mat):
Matrix *matrix):
Nan::AsyncWorker(callback),
bg(bg),
img_mat(img_mat) { // note: this makes a new cv::Mat, and so increments the ref count for the data without copying it
matrix(matrix) {
}
~AsyncBackgroundSubtractorWorker() {
// upon destroy, img_mat will reduce refcount on data by one
}
// Executed inside the worker-thread.
@ -328,9 +328,9 @@ public:
// wait here if already in apply - auto-release on scope exit
BGAutoMutex(bg->applymutex);
#if CV_MAJOR_VERSION >= 3
bg->subtractor->apply(this->img_mat, _fgMask);
bg->subtractor->apply(matrix->mat, _fgMask);
#else
bg->subtractor->operator()(this->img_mat, _fgMask);
bg->subtractor->operator()(matrix->mat, _fgMask);
#endif
}
@ -340,7 +340,10 @@ public:
void HandleOKCallback() {
Nan::HandleScope scope;
Local<Object> im_to_return = Matrix::CreateWrappedFromMat(_fgMask);
delete matrix;
matrix = NULL;
Local<Object> im_to_return = Matrix::CreateWrappedFromMat(_fgMask.clone());
Local<Value> argv[] = {
Nan::Null()
@ -356,7 +359,7 @@ public:
private:
BackgroundSubtractorWrap *bg;
cv::Mat img_mat;
Matrix *matrix;
cv::Mat _fgMask;
};
@ -395,7 +398,7 @@ NAN_METHOD(BackgroundSubtractorWrap::Apply) {
Nan::Callback *callback = new Nan::Callback(cb.As<Function>());
Matrix *_img = Nan::ObjectWrap::Unwrap<Matrix>(info[0]->ToObject());
Nan::AsyncQueueWorker(new AsyncBackgroundSubtractorWorker( callback, self, _img->mat));
Nan::AsyncQueueWorker(new AsyncBackgroundSubtractorWorker( callback, self, new Matrix::Matrix(_img)));
return;
} else { //synchronous - return the image
@ -426,7 +429,7 @@ NAN_METHOD(BackgroundSubtractorWrap::Apply) {
#else
self->subtractor->operator()(mat, _fgMask);
#endif
fgMask = Matrix::CreateWrappedFromMat(_fgMask);
fgMask = Matrix::CreateWrappedFromMat(_fgMask.clone());
}
mat.release();

View File

@ -161,13 +161,29 @@ NAN_METHOD(Matrix::New) {
info.GetReturnValue().Set(info.Holder());
}
//convenience factory method for creating a wrapped Matrix from a cv::Mat and tracking external memory correctly
//Convenience factory method for creating a wrapped Matrix from a cv::Mat and tracking external memory correctly.
// Always tracks the referenced matrix as external memory.
Local<Object> Matrix::CreateWrappedFromMat(cv::Mat mat){
Local < Object > result =
Nan::NewInstance(Nan::GetFunction(Nan::New(Matrix::constructor)).ToLocalChecked()).ToLocalChecked();
Matrix *m = Nan::ObjectWrap::Unwrap<Matrix>(result);
m->mat = mat;
Nan::AdjustExternalMemory(m->mat.rows * m->mat.cols * m->mat.elemSize());
Nan::AdjustExternalMemory(m->mat.dataend - m->mat.datastart);
return result;
}
//Convenience factory method for creating a wrapped Matrix from a cv::Mat and tracking external memory correctly.
// Only tracks the referenced matrix as external memory if the refcount does not exceed the base refcount.
// Useful for creating a wrapper Matrix around a Mat that is also referenced by another wrapper Matrix
Local<Object> Matrix::CreateWrappedFromMatIfNotReferenced(cv::Mat mat, int baseRefCount){
Local < Object > result =
Nan::NewInstance(Nan::GetFunction(Nan::New(Matrix::constructor)).ToLocalChecked()).ToLocalChecked();
Matrix *m = Nan::ObjectWrap::Unwrap<Matrix>(result);
m->mat = mat;
if (m->getWrappedRefCount() <= 2 + baseRefCount){ //one reference in m, one on the stack
Nan::AdjustExternalMemory(m->mat.dataend - m->mat.datastart);
}
return result;
}
@ -179,24 +195,28 @@ Matrix::Matrix() :
Matrix::Matrix(int rows, int cols) :
node_opencv::Matrix() {
mat = cv::Mat(rows, cols, CV_32FC3);
Nan::AdjustExternalMemory(mat.rows * mat.cols * mat.elemSize());
Nan::AdjustExternalMemory(mat.dataend - mat.datastart);
}
Matrix::Matrix(int rows, int cols, int type) :
node_opencv::Matrix() {
mat = cv::Mat(rows, cols, type);
Nan::AdjustExternalMemory(mat.rows * mat.cols * mat.elemSize());
Nan::AdjustExternalMemory(mat.dataend - mat.datastart);
}
Matrix::Matrix(Matrix *m) :
node_opencv::Matrix() {
mat = cv::Mat(m->mat);
}
Matrix::Matrix(cv::Mat m, cv::Rect roi) :
node_opencv::Matrix() {
mat = cv::Mat(m, roi);
Nan::AdjustExternalMemory(mat.rows * mat.cols * mat.elemSize());
}
Matrix::Matrix(int rows, int cols, int type, Local<Object> scalarObj) {
mat = cv::Mat(rows, cols, type);
Nan::AdjustExternalMemory(mat.rows * mat.cols * mat.elemSize());
Nan::AdjustExternalMemory(mat.dataend - mat.datastart);
if (mat.channels() == 3) {
mat.setTo(cv::Scalar(scalarObj->Get(0)->IntegerValue(),
scalarObj->Get(1)->IntegerValue(),
@ -212,8 +232,10 @@ Matrix::Matrix(int rows, int cols, int type, Local<Object> scalarObj) {
}
Matrix::~Matrix(){
int size = mat.rows * mat.cols * mat.elemSize();
Nan::AdjustExternalMemory(-1 * size);
if(getWrappedRefCount() == 1){ //if this holds the last reference to the Mat
int size = mat.dataend - mat.datastart;
Nan::AdjustExternalMemory(-1 * size);
}
}
NAN_METHOD(Matrix::Empty) {
@ -584,9 +606,9 @@ NAN_METHOD(Matrix::Crop) {
int width = info[2]->IntegerValue();
int height = info[3]->IntegerValue();
cv::Rect roi(x, y, width, height);
cv::Mat mat(self->mat, cv::Rect(x,y,width,height));
Local < Object > im_h = Matrix::CreateWrappedFromMat(self->mat(roi));
Local < Object > im_h = Matrix::CreateWrappedFromMatIfNotReferenced(mat, 1);
info.GetReturnValue().Set(im_h);
} else {
@ -784,10 +806,10 @@ NAN_METHOD(Matrix::ToBuffer) {
class AsyncToBufferWorker: public Nan::AsyncWorker {
public:
AsyncToBufferWorker(Nan::Callback *callback, cv::Mat mat, std::string ext,
AsyncToBufferWorker(Nan::Callback *callback, Matrix *matrix, std::string ext,
std::vector<int> params) :
Nan::AsyncWorker(callback),
mat(mat), // dulipcate mat, adding ref, but not copying data
matrix(matrix), // dulipcate mat, adding ref, but not copying data
ext(ext),
params(params) {
}
@ -799,13 +821,16 @@ public:
void Execute() {
std::vector<uchar> vec(0);
// std::vector<int> params(0);//CV_IMWRITE_JPEG_QUALITY 90
cv::imencode(ext, this->mat, vec, this->params);
cv::imencode(ext, matrix->mat, vec, this->params);
res = vec;
}
void HandleOKCallback() {
Nan::HandleScope scope;
delete matrix;
matrix = NULL;
Local<Object> buf = Nan::NewBuffer(res.size()).ToLocalChecked();
uchar* data = (uchar*) Buffer::Data(buf);
memcpy(data, &res[0], res.size());
@ -829,7 +854,7 @@ public:
}
private:
cv::Mat mat;
Matrix *matrix;
std::string ext;
std::vector<int> params;
std::vector<uchar> res;
@ -869,7 +894,7 @@ NAN_METHOD(Matrix::ToBufferAsync) {
}
Nan::Callback *callback = new Nan::Callback(cb.As<Function>());
Nan::AsyncQueueWorker(new AsyncToBufferWorker(callback, self->mat, ext, params));
Nan::AsyncQueueWorker(new AsyncToBufferWorker(callback, new Matrix(self), ext, params));
return;
}
@ -1062,9 +1087,9 @@ NAN_METHOD(Matrix::Save) {
// https://github.com/rvagg/nan/blob/c579ae858ae3208d7e702e8400042ba9d48fa64b/examples/async_pi_estimate/async.cc
class AsyncSaveWorker: public Nan::AsyncWorker {
public:
AsyncSaveWorker(Nan::Callback *callback, cv::Mat mat, char* filename) :
AsyncSaveWorker(Nan::Callback *callback, Matrix *matrix, char* filename) :
Nan::AsyncWorker(callback),
mat(mat),
matrix(matrix),
filename(filename) {
}
@ -1076,7 +1101,7 @@ public:
// here, so everything we need for input and output
// should go on `this`.
void Execute() {
res = cv::imwrite(this->filename, this->mat);
res = cv::imwrite(this->filename, matrix->mat);
}
// Executed when the async work is complete
@ -1085,6 +1110,9 @@ public:
void HandleOKCallback() {
Nan::HandleScope scope;
delete matrix;
matrix = NULL;
Local<Value> argv[] = {
Nan::Null(),
Nan::New<Number>(res)
@ -1098,7 +1126,7 @@ public:
}
private:
cv::Mat mat;
Matrix *matrix;
std::string filename;
int res;
};
@ -1115,7 +1143,7 @@ NAN_METHOD(Matrix::SaveAsync) {
REQ_FUN_ARG(1, cb);
Nan::Callback *callback = new Nan::Callback(cb.As<Function>());
Nan::AsyncQueueWorker(new AsyncSaveWorker(callback, self->mat, *filename));
Nan::AsyncQueueWorker(new AsyncSaveWorker(callback, new Matrix(self), *filename));
return;
}
@ -1166,9 +1194,9 @@ NAN_METHOD(Matrix::ConvertGrayscale) {
Nan::ThrowError("Image is no 3-channel");
}
int oldSize = self->mat.rows * self->mat.cols * self->mat.elemSize();
int oldSize = self->mat.dataend - self->mat.datastart;
cv::cvtColor(self->mat, self->mat, CV_BGR2GRAY);
int newSize = self->mat.rows * self->mat.cols * self->mat.elemSize();
int newSize = self->mat.dataend - self->mat.datastart;
Nan::AdjustExternalMemory(newSize - oldSize);
info.GetReturnValue().Set(Nan::Null());
@ -1300,7 +1328,7 @@ NAN_METHOD(Matrix::Sobel) {
Matrix *result = Nan::ObjectWrap::Unwrap<Matrix>(result_to_return);
cv::Sobel(self->mat, result->mat, ddepth, xorder, yorder, ksize, scale, delta, borderType);
Nan::AdjustExternalMemory(result->mat.rows * result->mat.cols * result->mat.elemSize());
Nan::AdjustExternalMemory(result->mat.dataend - result->mat.datastart);
info.GetReturnValue().Set(result_to_return);
}
@ -1314,7 +1342,7 @@ NAN_METHOD(Matrix::Copy) {
Nan::NewInstance(Nan::GetFunction(Nan::New(Matrix::constructor)).ToLocalChecked()).ToLocalChecked();
Matrix *img = Nan::ObjectWrap::Unwrap<Matrix>(img_to_return);
self->mat.copyTo(img->mat);
Nan::AdjustExternalMemory(img->mat.rows * img->mat.cols * img->mat.elemSize());
Nan::AdjustExternalMemory(img->mat.dataend - img->mat.datastart);
info.GetReturnValue().Set(img_to_return);
}
@ -1334,7 +1362,7 @@ NAN_METHOD(Matrix::Flip) {
Local<Object> img_to_return = Nan::NewInstance(Nan::GetFunction(Nan::New(Matrix::constructor)).ToLocalChecked()).ToLocalChecked();
Matrix *img = Nan::ObjectWrap::Unwrap<Matrix>(img_to_return);
cv::flip(self->mat, img->mat, flipCode);
Nan::AdjustExternalMemory(img->mat.rows * img->mat.cols * img->mat.elemSize());
Nan::AdjustExternalMemory(img->mat.dataend - img->mat.datastart);
info.GetReturnValue().Set(img_to_return);
}
@ -1357,7 +1385,7 @@ NAN_METHOD(Matrix::ROI) {
cv::Mat roi(self->mat, cv::Rect(x,y,w,h));
// Although it's an image to return, it is in fact a pointer to ROI of parent matrix
Local<Object> img_to_return = Matrix::CreateWrappedFromMat(roi);
Local<Object> img_to_return = Matrix::CreateWrappedFromMatIfNotReferenced(roi, 1);
info.GetReturnValue().Set(img_to_return);
}
@ -1397,7 +1425,7 @@ NAN_METHOD(Matrix::Dct) {
Local<Object> out = Nan::NewInstance(Nan::GetFunction(Nan::New(Matrix::constructor)).ToLocalChecked()).ToLocalChecked();
Matrix *m_out = Nan::ObjectWrap::Unwrap<Matrix>(out);
m_out->mat.create(cols, rows, CV_32F);
Nan::AdjustExternalMemory(m_out->mat.rows * m_out->mat.cols * m_out->mat.elemSize());
Nan::AdjustExternalMemory(m_out->mat.dataend - m_out->mat.datastart);
cv::dct(self->mat, m_out->mat);
@ -1414,7 +1442,7 @@ NAN_METHOD(Matrix::Idct) {
Local<Object> out = Nan::NewInstance(Nan::GetFunction(Nan::New(Matrix::constructor)).ToLocalChecked()).ToLocalChecked();
Matrix *m_out = Nan::ObjectWrap::Unwrap<Matrix>(out);
m_out->mat.create(cols, rows, CV_32F);
Nan::AdjustExternalMemory(m_out->mat.rows * m_out->mat.cols * m_out->mat.elemSize());
Nan::AdjustExternalMemory(m_out->mat.dataend - m_out->mat.datastart);
cv::idct(self->mat, m_out->mat);
@ -1451,19 +1479,15 @@ NAN_METHOD(Matrix::Add) {
Matrix *src1 = Nan::ObjectWrap::Unwrap<Matrix>(info[0]->ToObject());
Local<Object> out = Nan::NewInstance(Nan::GetFunction(Nan::New(Matrix::constructor)).ToLocalChecked()).ToLocalChecked();
Matrix *m_out = Nan::ObjectWrap::Unwrap<Matrix>(out);
m_out->mat.create(cols, rows, self->mat.type());
Nan::AdjustExternalMemory(m_out->mat.rows * m_out->mat.cols * m_out->mat.elemSize());
try {
cv::add(self->mat, src1->mat, m_out->mat);
cv::Mat outputmat = cv::Mat(cols, rows, self->mat.type());
cv::add(self->mat, src1->mat, outputmat);
Local<Object> out = CreateWrappedFromMat(outputmat);
info.GetReturnValue().Set(out);
} catch(cv::Exception& e ) {
const char* err_msg = e.what();
Nan::ThrowError(err_msg);
}
info.GetReturnValue().Set(out);
}
NAN_METHOD(Matrix::BitwiseXor) {
@ -1916,10 +1940,10 @@ cv::Rect* setRect(Local<Object> objRect, cv::Rect &result) {
class ResizeASyncWorker: public Nan::AsyncWorker {
public:
ResizeASyncWorker(Nan::Callback *callback, cv::Mat image, cv::Size size, double fx, double fy, int interpolation) :
ResizeASyncWorker(Nan::Callback *callback, Matrix *image, cv::Size size, double fx, double fy, int interpolation) :
Nan::AsyncWorker(callback),
image(image), // here, the cv::Mat is duplicated, adding to refcount without data copy
dest(NULL),
image(image),
dest(cv::Mat()),
size(size),
fx(fx),
fy(fy),
@ -1928,17 +1952,14 @@ public:
}
~ResizeASyncWorker() {
// don't leave this if it was allocated
// could happen if NaN does not call HandleSuccess?
delete dest;
dest = NULL;
// cv::Mat image will be deleted, which will reduce refcount
// Any cleanup we needed to do could be done here.
// Clean up of the input image Matrix and the destination cv::Mat
// should be handled automatically by destructors.
}
void Execute() {
try {
dest = new Matrix();
cv::resize(image, dest->mat, size, fx, fy, interpolation);
cv::resize(image->mat, dest, size, fx, fy, interpolation);
success = 1;
} catch(...){
success = 0;
@ -1950,9 +1971,10 @@ public:
if (success){
try{
Local<Object> im_to_return = Matrix::CreateWrappedFromMat(dest->mat);
delete dest;
dest = NULL;
Local<Object> im_to_return = Matrix::CreateWrappedFromMat(dest);
delete image;
image = NULL;
dest.release(); //release our refcount before handing it back to the callback
Local<Value> argv[] = {
Nan::Null(), // err
@ -1965,8 +1987,6 @@ public:
Nan::FatalException(try_catch);
}
} catch (...){
delete dest;
dest = NULL;
Local<Value> argv[] = {
Nan::New("C++ exception wrapping response").ToLocalChecked(), // err
Nan::Null() // result
@ -1979,9 +1999,6 @@ public:
}
}
} else {
delete dest;
dest = NULL;
Local<Value> argv[] = {
Nan::New("C++ exception").ToLocalChecked(), // err
Nan::Null() //result
@ -1996,8 +2013,8 @@ public:
}
private:
cv::Mat image;
Matrix *dest;
Matrix *image;
cv::Mat dest;
cv::Size size;
double fx;
double fy;
@ -2062,17 +2079,17 @@ NAN_METHOD(Matrix::Resize) {
if (isAsync){
REQ_FUN_ARG(numargs-1, cb);
Nan::Callback *callback = new Nan::Callback(cb.As<Function>());
Nan::AsyncQueueWorker(new ResizeASyncWorker(callback, self->mat, size, fx, fy, interpolation));
Nan::AsyncQueueWorker(new ResizeASyncWorker(callback, new Matrix(self), size, fx, fy, interpolation));
info.GetReturnValue().Set(Nan::Null());
} else {
try{
Matrix *self = Nan::ObjectWrap::Unwrap<Matrix>(info.This());
int oldSize = self->mat.rows * self->mat.cols * self->mat.elemSize();
int oldSize = (self->getWrappedRefCount() == 1) ? self->mat.dataend - self->mat.datastart : 0;
cv::Mat res = cv::Mat(x, y, CV_32FC3);
cv::resize(self->mat, res, cv::Size(x, y), 0, 0, interpolation);
~self->mat;
self->mat = res;
int newSize = self->mat.rows * self->mat.cols * self->mat.elemSize();
int newSize = self->mat.dataend - self->mat.datastart;
Nan::AdjustExternalMemory(newSize - oldSize);
} catch (...){
return Nan::ThrowError("c++ Exception processing resize");
@ -2302,13 +2319,12 @@ NAN_METHOD(Matrix::Threshold) {
}
}
Local < Object > img_to_return =
Nan::NewInstance(Nan::GetFunction(Nan::New(Matrix::constructor)).ToLocalChecked()).ToLocalChecked();
Matrix *img = Nan::ObjectWrap::Unwrap<Matrix>(img_to_return);
self->mat.copyTo(img->mat);
cv::Mat outputmat = cv::Mat();
self->mat.copyTo(outputmat);
cv::threshold(self->mat, img->mat, threshold, maxVal, typ);
Nan::AdjustExternalMemory(img->mat.rows * img->mat.cols * img->mat.elemSize());
cv::threshold(self->mat, outputmat, threshold, maxVal, typ);
Local < Object > img_to_return = CreateWrappedFromMat(outputmat);
info.GetReturnValue().Set(img_to_return);
}
@ -2322,14 +2338,13 @@ NAN_METHOD(Matrix::AdaptiveThreshold) {
double blockSize = info[3]->NumberValue();
double C = info[4]->NumberValue();
Local < Object > img_to_return =
Nan::NewInstance(Nan::GetFunction(Nan::New(Matrix::constructor)).ToLocalChecked()).ToLocalChecked();
Matrix *img = Nan::ObjectWrap::Unwrap<Matrix>(img_to_return);
self->mat.copyTo(img->mat);
cv::Mat outputmat = cv::Mat();
self->mat.copyTo(outputmat);
cv::adaptiveThreshold(self->mat, img->mat, maxVal, adaptiveMethod,
cv::adaptiveThreshold(self->mat, outputmat, maxVal, adaptiveMethod,
thresholdType, blockSize, C);
Nan::AdjustExternalMemory(img->mat.rows * img->mat.cols * img->mat.elemSize());
Local < Object > img_to_return = CreateWrappedFromMat(outputmat);
info.GetReturnValue().Set(img_to_return);
}
@ -2339,18 +2354,14 @@ NAN_METHOD(Matrix::MeanStdDev) {
Matrix *self = Nan::ObjectWrap::Unwrap<Matrix>(info.This());
Local<Object> mean = Nan::NewInstance(Nan::GetFunction(Nan::New(Matrix::constructor)).ToLocalChecked()).ToLocalChecked();
Matrix *m_mean = Nan::ObjectWrap::Unwrap<Matrix>(mean);
Local<Object> stddev = Nan::NewInstance(Nan::GetFunction(Nan::New(Matrix::constructor)).ToLocalChecked()).ToLocalChecked();
Matrix *m_stddev = Nan::ObjectWrap::Unwrap<Matrix>(stddev);
cv::Mat meanMat = cv::Mat();
cv::Mat stddevMat = cv::Mat();
cv::meanStdDev(self->mat, m_mean->mat, m_stddev->mat);
Nan::AdjustExternalMemory(m_mean->mat.rows * m_mean->mat.cols * m_mean->mat.elemSize());
Nan::AdjustExternalMemory(m_stddev->mat.rows * m_stddev->mat.cols * m_stddev->mat.elemSize());
cv::meanStdDev(self->mat, meanMat, stddevMat);
Local<Object> data = Nan::New<Object>();
data->Set(Nan::New<String>("mean").ToLocalChecked(), mean);
data->Set(Nan::New<String>("stddev").ToLocalChecked(), stddev);
data->Set(Nan::New<String>("mean").ToLocalChecked(), CreateWrappedFromMat(meanMat));
data->Set(Nan::New<String>("stddev").ToLocalChecked(), CreateWrappedFromMat(stddevMat));
info.GetReturnValue().Set(data);
}
@ -2479,9 +2490,9 @@ NAN_METHOD(Matrix::CvtColor) {
Nan::ThrowTypeError("Conversion code is unsupported");
}
int oldSize = self->mat.rows * self->mat.cols * self->mat.elemSize();
int oldSize = self->mat.dataend - self->mat.datastart;
cv::cvtColor(self->mat, self->mat, iTransform);
int newSize = self->mat.rows * self->mat.cols * self->mat.elemSize();
int newSize = self->mat.dataend - self->mat.datastart;
if(oldSize != newSize){
Nan::AdjustExternalMemory(newSize - oldSize);
}
@ -2508,7 +2519,7 @@ NAN_METHOD(Matrix::Split) {
size = channels.size();
v8::Local<v8::Array> arrChannels = Nan::New<Array>(size);
for (unsigned int i = 0; i < size; i++) {
Local<Object> matObject = Matrix::CreateWrappedFromMat(channels[i]);
Local<Object> matObject = Matrix::CreateWrappedFromMatIfNotReferenced(channels[i], 1);
arrChannels->Set(i, matObject);
}
@ -2524,7 +2535,7 @@ NAN_METHOD(Matrix::Merge) {
if (!info[0]->IsArray()) {
Nan::ThrowTypeError("The argument must be an array");
}
int oldSize = self->mat.rows * self->mat.cols * self->mat.elemSize();
int oldSize = self->mat.dataend - self->mat.datastart;
v8::Local<v8::Array> jsChannels = v8::Local<v8::Array>::Cast(info[0]);
unsigned int L = jsChannels->Length();
@ -2534,7 +2545,7 @@ NAN_METHOD(Matrix::Merge) {
vChannels[i] = matObject->mat;
}
cv::merge(vChannels, self->mat);
int newSize = self->mat.rows * self->mat.cols * self->mat.elemSize();
int newSize = self->mat.dataend - self->mat.datastart;
Nan::AdjustExternalMemory(newSize - oldSize);
return;
@ -2694,7 +2705,7 @@ NAN_METHOD(Matrix::MatchTemplateByMatrix) {
int cols = self->mat.cols - templ->mat.cols + 1;
int rows = self->mat.rows - templ->mat.rows + 1;
m_out->mat.create(cols, rows, CV_32FC1);
Nan::AdjustExternalMemory(m_out->mat.rows * m_out->mat.cols * m_out->mat.elemSize());
Nan::AdjustExternalMemory(m_out->mat.dataend - m_out->mat.datastart);
/*
TM_SQDIFF =0
@ -2729,7 +2740,7 @@ NAN_METHOD(Matrix::MatchTemplate) {
int cols = self->mat.cols - templ.cols + 1;
int rows = self->mat.rows - templ.rows + 1;
m_out->mat.create(cols, rows, CV_32FC1);
Nan::AdjustExternalMemory(m_out->mat.rows * m_out->mat.cols * m_out->mat.elemSize());
Nan::AdjustExternalMemory(m_out->mat.dataend - m_out->mat.datastart);
/*
TM_SQDIFF =0
@ -3038,7 +3049,7 @@ NAN_METHOD(Matrix::Reshape) {
JSTHROW("Invalid number of arguments");
}
Local<Object> img_to_return = Matrix::CreateWrappedFromMat(self->mat.reshape(cn, rows));
Local<Object> img_to_return = Matrix::CreateWrappedFromMatIfNotReferenced(self->mat.reshape(cn, rows),0);
info.GetReturnValue().Set(img_to_return);
}
@ -3047,9 +3058,13 @@ NAN_METHOD(Matrix::Release) {
Nan::HandleScope scope;
Matrix *self = Nan::ObjectWrap::Unwrap<Matrix>(info.This());
int size = self->mat.rows * self->mat.cols * self->mat.elemSize();
if(self->getWrappedRefCount() == 1){
int size = self->mat.dataend - self->mat.datastart;
Nan::AdjustExternalMemory(-1 * size);
}
self->mat.release();
Nan::AdjustExternalMemory(-1 * size);
return;
}
@ -3065,25 +3080,29 @@ NAN_METHOD(Matrix::Release) {
//}
NAN_METHOD(Matrix::GetrefCount) {
Nan::HandleScope scope;
Matrix *self = Nan::ObjectWrap::Unwrap<Matrix>(info.This());
int refcount = -1;
int Matrix::getWrappedRefCount(){
int refcount = -1;
#if CV_MAJOR_VERSION >= 3
if (self->mat.u){
refcount = self->mat.u->refcount;
if (mat.u){
refcount = mat.u->refcount;
} else {
refcount = -1; // indicates no reference ptr
}
#else
if (self->mat.refcount){
refcount = *(self->mat.refcount);
if (mat.refcount){
refcount = *(mat.refcount);
} else {
refcount = -1; // indicates no reference ptr
}
#endif
#endif
return refcount;
}
NAN_METHOD(Matrix::GetrefCount) {
Nan::HandleScope scope;
Matrix *self = Nan::ObjectWrap::Unwrap<Matrix>(info.This());
int refcount = self->getWrappedRefCount();
info.GetReturnValue().Set(Nan::New<Number>(refcount));
return;

View File

@ -8,7 +8,10 @@ public:
static void Init(Local<Object> target);
static NAN_METHOD(New);
static Local<Object> CreateWrappedFromMat(cv::Mat mat);
static Local<Object> CreateWrappedFromMatIfNotReferenced(cv::Mat mat, int baseRefCount);
int getWrappedRefCount();
Matrix();
Matrix(Matrix *other);
Matrix(cv::Mat other, cv::Rect roi);
Matrix(int rows, int cols);
Matrix(int rows, int cols, int type);

View File

@ -49,6 +49,7 @@ public:
try{
Local<Object> im_to_return = Matrix::CreateWrappedFromMat(outputmat);
outputmat.release();
Local<Value> argv[] = {
Nan::Null(),
@ -110,6 +111,7 @@ public:
Nan::HandleScope scope;
Local<Object> im_to_return = Matrix::CreateWrappedFromMat(outputmat);
outputmat.release();
Local<Value> argv[] = {
Nan::Null(),
@ -165,7 +167,7 @@ NAN_METHOD(OpenCV::ReadImageAsync) {
width = info[0]->Uint32Value();
height = info[1]->Uint32Value();
Local<Object> img_to_return = Matrix::CreateWrappedFromMat(*(new cv::Mat(width, height, type)));
Local<Object> img_to_return = Matrix::CreateWrappedFromMat(cv::Mat(width, height, type));
if (callback_arg < 0){
info.GetReturnValue().Set(img_to_return);
return;
@ -287,7 +289,7 @@ NAN_METHOD(OpenCV::ReadImage) {
}
width = info[0]->Uint32Value();
height = info[1]->Uint32Value();
mat = *(new cv::Mat(width, height, type));
mat = cv::Mat(width, height, type);
} else if (info[0]->IsString()) {
std::string filename = std::string(*Nan::Utf8String(info[0]->ToString()));
@ -318,7 +320,7 @@ NAN_METHOD(OpenCV::ReadImage) {
}
img->mat = mat;
Nan::AdjustExternalMemory(img->mat.rows * img->mat.cols * img->mat.elemSize());
Nan::AdjustExternalMemory(img->mat.dataend - img->mat.datastart);
} catch (cv::Exception& e) {
argv[0] = Nan::Error(e.what());
argv[1] = Nan::Null();

View File

@ -40,7 +40,7 @@ void VideoCaptureWrap::Init(Local<Object> target) {
Nan::SetPrototypeMethod(ctor, "getFPS", GetFPS);
Nan::SetPrototypeMethod(ctor, "setFPS", SetFPS);
Nan::SetPrototypeMethod(ctor, "release", Release);
Nan::SetPrototypeMethod(ctor, "ReadSync", ReadSync);
Nan::SetPrototypeMethod(ctor, "readSync", ReadSync);
Nan::SetPrototypeMethod(ctor, "grab", Grab);
Nan::SetPrototypeMethod(ctor, "retrieve", Retrieve);
@ -238,6 +238,7 @@ public:
Nan::HandleScope scope;
Local<Object> im_to_return = Matrix::CreateWrappedFromMat(mat);
mat.release();
Local<Value> argv[] = {
Nan::Null()
@ -274,11 +275,10 @@ NAN_METHOD(VideoCaptureWrap::ReadSync) {
Nan::HandleScope scope;
VideoCaptureWrap *v = Nan::ObjectWrap::Unwrap<VideoCaptureWrap>(info.This());
Local<Object> im_to_return= Nan::NewInstance(Nan::GetFunction(Nan::New(Matrix::constructor)).ToLocalChecked()).ToLocalChecked();
Matrix *img = Nan::ObjectWrap::Unwrap<Matrix>(im_to_return);
cv::Mat outputmat = cv::Mat();
v->cap.read(outputmat);
v->cap.read(img->mat);
Nan::AdjustExternalMemory(img->mat.rows * img->mat.cols * img->mat.elemSize());
Local<Object> im_to_return = Matrix::CreateWrappedFromMat(outputmat);
info.GetReturnValue().Set(im_to_return);
}

980
test/memory.js Normal file
View File

@ -0,0 +1,980 @@
require("v8").setFlagsFromString('--expose_gc');
var gc = require("vm").runInNewContext('gc');
var fs = require('fs')
, path = require('path')
, test = require('tape')
, cv = require('../lib/opencv');
var IMAGE_PATH = path.resolve(__dirname, '../examples/files', 'mona.png');
var TEMP_SAVE_PATH = path.resolve(__dirname, '../examples/tmp', 'out.jpg');
// These tests check that every function that creates or modifies a Matrix handles its externally tracked memory correctly.
// Since the memory tracker uses OpenCV's reference counting to determine when to tell Node about memory changes,
// it is important that only Matrix objects that Javascript knows about retain references to internal OpenCV Mat objects.
// Reference counts for newly created objects should ususally therefore be 1, and releasing them should alter the
// externally tracked memory appropriately.
// Note that garbage collection could run at any time, which could interfere with measurements of external memory,
// so these tests manually run garbage collection as part of their setup to minimize this possibility.
//********************
// Image Reading
//********************
test("readImage", t=>{
gc();
var startingMemory = process.memoryUsage().external;
var image = cv.readImage(IMAGE_PATH);
t.equal(image.getrefCount(), 1);
t.equal(process.memoryUsage().external - startingMemory, 1134000); //image is tracked as external memory
image.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
test("readImage creating a new 100x100 matrix", t=>{
gc();
var startingMemory = process.memoryUsage().external;
var image = cv.readImage(100,100);
t.equal(image.getrefCount(), 1);
t.equal(process.memoryUsage().external - startingMemory, 80000); //image is tracked as external memory
image.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
test("readImage from data buffer", t=>{
gc();
var buffer = fs.readFileSync(IMAGE_PATH);
var startingMemory = process.memoryUsage().external;
var image = cv.readImage(buffer);
t.equal(image.getrefCount(), 1);
t.equal(process.memoryUsage().external - startingMemory, 1134000); //image is tracked as external memory
image.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
test("readImage (callback pattern)", t=>{
gc();
var startingMemory = process.memoryUsage().external;
cv.readImage(IMAGE_PATH, (err, image) => {
t.equal(image.getrefCount(), 1);
t.equal(process.memoryUsage().external - startingMemory, 1134000); //image is tracked as external memory
image.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
});
test("readImage creating a new 100x100 matrix (callback pattern)", t=>{
gc();
var startingMemory = process.memoryUsage().external;
cv.readImage(100, 100, (err, image) => {
t.equal(image.getrefCount(), 1);
t.equal(process.memoryUsage().external - startingMemory, 80000); //image is tracked as external memory
image.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
});
test("readImage from data buffer (callback pattern)", t=>{
gc();
var buffer = fs.readFileSync(IMAGE_PATH);
var startingMemory = process.memoryUsage().external;
cv.readImage(buffer, (err, image) => {
t.equal(image.getrefCount(), 1);
t.equal(process.memoryUsage().external - startingMemory, 1134000); //image is tracked as external memory
image.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
});
test("readImageAsync", t=>{
gc();
var startingMemory = process.memoryUsage().external;
cv.readImageAsync(IMAGE_PATH, (err, image) => {
t.equal(image.getrefCount(), 1);
t.equal(process.memoryUsage().external - startingMemory, 1134000); //image is tracked as external memory
image.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
});
test("readImageAsync creating a new 100x100 matrix", t=>{
gc();
var startingMemory = process.memoryUsage().external;
cv.readImageAsync(100, 100, (err, image) => {
t.equal(image.getrefCount(), 1);
t.equal(process.memoryUsage().external - startingMemory, 80000); //image is tracked as external memory
image.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
});
test("readImageAsync from data buffer", t=>{
gc();
var buffer = fs.readFileSync(IMAGE_PATH);
var startingMemory = process.memoryUsage().external;
cv.readImageAsync(buffer, (err, image) => {
t.equal(image.getrefCount(), 1);
t.equal(process.memoryUsage().external - startingMemory, 1134000); //image is tracked as external memory
image.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
});
test("video capture async", t=>{
gc();
var vid = new cv.VideoCapture(path.resolve(__dirname, '../examples/files', 'motion.mov'));
var startingMemory = process.memoryUsage().external;
vid.read( (err, im) => {
t.equal(im.getrefCount(), 1);
t.equal(process.memoryUsage().external - startingMemory, 545280); //image is tracked as external memory
im.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
});
test("video capture sync", t=>{
gc();
var vid = new cv.VideoCapture(path.resolve(__dirname, '../examples/files', 'motion.mov'));
var startingMemory = process.memoryUsage().external;
var im = vid.readSync();
t.equal(im.getrefCount(), 1);
t.equal(process.memoryUsage().external - startingMemory, 545280);
im.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
//********************
// Matrix Constructors
//********************
// Base constructor, doesn't actually allocate any memory
test("Matrix()", t=>{
gc();
var startingMemory = process.memoryUsage().external;
var image = new cv.Matrix();
t.equal(image.getrefCount(), -1);
t.equal(process.memoryUsage().external - startingMemory, 0);
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
// Constructor with a size
test("Matrix(int, int)", t=>{
gc();
var startingMemory = process.memoryUsage().external;
var image = new cv.Matrix(100, 100);
t.equal(image.getrefCount(), 1);
t.equal(process.memoryUsage().external - startingMemory, 120000); //100 * 100 * size of CV_32FC3 (12)
image.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
// Constructor with a size and type
test("Matrix(int, int, type)", t=>{
gc();
var startingMemory = process.memoryUsage().external;
var image = new cv.Matrix(100, 100, cv.Constants.CV_8UC1);
t.equal(image.getrefCount(), 1);
t.equal(process.memoryUsage().external - startingMemory, 10000); //100 * 100
image.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
// Constructor with a size, type, and initial values
test("Matrix(int, int, type, [values])", t=>{
gc();
var startingMemory = process.memoryUsage().external;
var image = new cv.Matrix(100, 100, cv.Constants.CV_8UC3, [0,0,0]);
t.equal(image.getrefCount(), 1);
t.equal(process.memoryUsage().external - startingMemory, 30000); //100 * 100 * 3
image.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
// Constructor with an existing matrix and a region of interest
test("Matrix(Matrix, x, y, w, h)", t=>{
gc();
var startingMemory = process.memoryUsage().external;
var originalImage = new cv.Matrix(100, 100, cv.Constants.CV_8UC3, [0,0,0]);
t.equal(originalImage.getrefCount(), 1);
t.equal(process.memoryUsage().external - startingMemory, 30000); //100 * 100 * 3
var image = new cv.Matrix(originalImage, 25, 25, 50, 50); //this should share memory with the original
t.equal(image.getrefCount(), 2); //so the refcount goes up
t.equal(process.memoryUsage().external - startingMemory, 30000); //but the memory usage does not
originalImage.release();
t.equal(originalImage.getrefCount(), -1);
t.equal(image.getrefCount(), 1);
t.equal(process.memoryUsage().external - startingMemory, 30000);
image.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
test("Matrix.Zeros", t=>{
gc();
var startingMemory = process.memoryUsage().external;
var image = cv.Matrix.Zeros(100, 100);
t.equal(image.getrefCount(), 1);
t.equal(process.memoryUsage().external - startingMemory, 80000); //100 * 100 * size of CV_64FC1
image.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
test("Matrix.Ones", t=>{
gc();
var startingMemory = process.memoryUsage().external;
var image = cv.Matrix.Ones(100, 100);
t.equal(image.getrefCount(), 1);
t.equal(process.memoryUsage().external - startingMemory, 80000); //100 * 100 * size of CV_64FC1
image.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
test("Matrix.Eye", t=>{
gc();
var startingMemory = process.memoryUsage().external;
var image = cv.Matrix.Eye(100, 100);
t.equal(image.getrefCount(), 1);
t.equal(process.memoryUsage().external - startingMemory, 80000); //100 * 100 * size of CV_64FC1
image.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
//********************
// Matrix Functions
//********************
test("Matrix clone", t=>{
gc();
var startingMemory = process.memoryUsage().external;
var originalImage = new cv.Matrix(100, 100, cv.Constants.CV_8UC3, [0,0,0]);
t.equal(originalImage.getrefCount(), 1);
t.equal(process.memoryUsage().external - startingMemory, 30000); //100 * 100 * 3
var image = originalImage.clone();
t.equal(image.getrefCount(), 1);
t.equal(process.memoryUsage().external - startingMemory, 60000);
originalImage.release();
t.equal(originalImage.getrefCount(), -1);
t.equal(image.getrefCount(), 1);
t.equal(process.memoryUsage().external - startingMemory, 30000);
image.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
test("Matrix crop", t=>{
gc();
var startingMemory = process.memoryUsage().external;
var originalImage = new cv.Matrix(100, 100, cv.Constants.CV_8UC3, [0,0,0]);
t.equal(originalImage.getrefCount(), 1);
t.equal(process.memoryUsage().external - startingMemory, 30000); //100 * 100 * 3
t.equal(originalImage.height(), 100);
var image = originalImage.crop(25, 25, 50, 50); //crops share memory with the original
t.equal(originalImage.height(), 100);
t.equal(image.height(), 50);
t.equal(image.getrefCount(), 2);
t.equal(process.memoryUsage().external - startingMemory, 30000);
originalImage.release();
t.equal(originalImage.getrefCount(), -1);
t.equal(image.getrefCount(), 1);
t.equal(process.memoryUsage().external - startingMemory, 30000);
image.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
//ROI in this implementation is basically the same thing as crop
test("Matrix roi", t=>{
gc();
var startingMemory = process.memoryUsage().external;
var originalImage = new cv.Matrix(100, 100, cv.Constants.CV_8UC3, [0,0,0]);
t.equal(originalImage.getrefCount(), 1);
t.equal(process.memoryUsage().external - startingMemory, 30000); //100 * 100 * 3
t.equal(originalImage.height(), 100);
var image = originalImage.roi(25, 25, 50, 50); //ROIs share memory with the original
t.equal(originalImage.height(), 100);
t.equal(image.height(), 50);
t.equal(image.getrefCount(), 2);
t.equal(process.memoryUsage().external - startingMemory, 30000);
originalImage.release();
t.equal(originalImage.getrefCount(), -1);
t.equal(image.getrefCount(), 1);
t.equal(process.memoryUsage().external - startingMemory, 30000);
image.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
test("Matrix convertGrayscale", t=>{
gc();
var startingMemory = process.memoryUsage().external;
var originalImage = new cv.Matrix(100, 100, cv.Constants.CV_8UC3, [0,0,0]);
t.equal(process.memoryUsage().external - startingMemory, 30000); //100 * 100 * 3
originalImage.convertGrayscale();
t.equal(process.memoryUsage().external - startingMemory, 10000); //grayscale takes less space
t.equal(originalImage.getrefCount(), 1);
originalImage.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
test("Matrix sobel", t=>{
gc();
var startingMemory = process.memoryUsage().external;
var originalImage = new cv.Matrix(100, 100, cv.Constants.CV_8UC3, [0,0,0]);
t.equal(process.memoryUsage().external - startingMemory, 30000); //100 * 100 * 3
var resultImage = originalImage.sobel(cv.Constants.CV_16S, 1, 1);
t.equal(process.memoryUsage().external - startingMemory, 90000); //our original 30k image plus our new 60k one
t.equal(originalImage.getrefCount(), 1);
t.equal(resultImage.getrefCount(), 1);
originalImage.release();
resultImage.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
test("Matrix copy", t=>{
gc();
var startingMemory = process.memoryUsage().external;
var originalImage = new cv.Matrix(100, 100, cv.Constants.CV_8UC3, [0,0,0]);
t.equal(process.memoryUsage().external - startingMemory, 30000); //100 * 100 * 3
var resultImage = originalImage.copy(0);
t.equal(process.memoryUsage().external - startingMemory, 60000); //our original image plus our new one
t.equal(originalImage.getrefCount(), 1);
t.equal(resultImage.getrefCount(), 1);
originalImage.release();
resultImage.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
test("Matrix flip", t=>{
gc();
var startingMemory = process.memoryUsage().external;
var originalImage = new cv.Matrix(100, 100, cv.Constants.CV_8UC3, [0,0,0]);
t.equal(process.memoryUsage().external - startingMemory, 30000); //100 * 100 * 3
var resultImage = originalImage.flip(0);
t.equal(process.memoryUsage().external - startingMemory, 60000); //our original image plus our new one
t.equal(originalImage.getrefCount(), 1);
t.equal(resultImage.getrefCount(), 1);
originalImage.release();
resultImage.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
test("Matrix dct", t=>{
gc();
var startingMemory = process.memoryUsage().external;
var originalImage = new cv.Matrix(100, 100, cv.Constants.CV_32F);
t.equal(process.memoryUsage().external - startingMemory, 40000); //100 * 100 * 4
var resultImage = originalImage.dct();
t.equal(process.memoryUsage().external - startingMemory, 80000); //our original image plus our new one
t.equal(originalImage.getrefCount(), 1);
t.equal(resultImage.getrefCount(), 1);
originalImage.release();
resultImage.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
test("Matrix idct", t=>{
gc();
var startingMemory = process.memoryUsage().external;
var originalImage = new cv.Matrix(100, 100, cv.Constants.CV_32F);
t.equal(process.memoryUsage().external - startingMemory, 40000); //100 * 100 * 4
var resultImage = originalImage.idct();
t.equal(process.memoryUsage().external - startingMemory, 80000); //our original image plus our new one
t.equal(originalImage.getrefCount(), 1);
t.equal(resultImage.getrefCount(), 1);
originalImage.release();
resultImage.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
test("Matrix add", t=>{
gc();
var startingMemory = process.memoryUsage().external;
var src1 = new cv.Matrix.Ones(100, 100);
var src2 = new cv.Matrix.Ones(100, 100);
t.equal(process.memoryUsage().external - startingMemory, 160000);
var resultMatrix = src1.add(src2);
t.equal(resultMatrix.get(0,0), 2); //just making sure the result is correct
t.equal(process.memoryUsage().external - startingMemory, 240000);
t.equal(src1.getrefCount(), 1);
t.equal(src2.getrefCount(), 1);
t.equal(resultMatrix.getrefCount(), 1);
src1.release();
src2.release();
resultMatrix.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
test("Matrix resize", t=>{
gc();
var startingMemory = process.memoryUsage().external;
var image = new cv.Matrix(100, 100, cv.Constants.CV_8UC3, [0,0,0]);
t.equal(process.memoryUsage().external - startingMemory, 30000); //100 * 100 * 3
image.resize(50, 50);
t.equal(process.memoryUsage().external - startingMemory, 7500); //50 * 50 * 3
image.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
test("Matrix resize async", t=>{
gc();
var startingMemory = process.memoryUsage().external;
var image = new cv.Matrix(100, 100, cv.Constants.CV_8UC3, [0,0,0]);
t.equal(process.memoryUsage().external - startingMemory, 30000); //100 * 100 * 3
image.resize(50, 50, (err, resizedImage)=>{
t.equal(image.height(), 100); //we have both the original and the resized image
t.equal(resizedImage.height(), 50);
t.equal(image.getrefCount(), 1);
t.equal(resizedImage.getrefCount(), 1);
t.equal(process.memoryUsage().external - startingMemory, 37500);
image.release();
resizedImage.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
});
test("Matrix resize async edge case", t=>{
gc();
var startingMemory = process.memoryUsage().external;
var image = new cv.Matrix(1000, 1000, cv.Constants.CV_8UC3, [0,0,0]);
t.equal(process.memoryUsage().external - startingMemory, 3000000); //100 * 100 * 3
image.resize(50, 50, (err, resizedImage)=>{
t.equal(image.getrefCount(), -1); //this happens second, image should have been released already
t.equal(resizedImage.getrefCount(), 1);
t.equal(process.memoryUsage().external - startingMemory, 7500);
resizedImage.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
image.release(); //this happens first
});
test("Matrix threshold", t=>{
gc();
var startingMemory = process.memoryUsage().external;
var originalImage = new cv.Matrix(100, 100, cv.Constants.CV_8U);
t.equal(process.memoryUsage().external - startingMemory, 10000); //100 * 100
var resultImage = originalImage.threshold(1,1);
t.equal(process.memoryUsage().external - startingMemory, 20000); //our original image plus our new one
t.equal(originalImage.getrefCount(), 1);
t.equal(resultImage.getrefCount(), 1);
originalImage.release();
resultImage.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
test("Matrix adaptiveThreshold", t=>{
gc();
var startingMemory = process.memoryUsage().external;
var originalImage = new cv.Matrix(100, 100, cv.Constants.CV_8U);
t.equal(process.memoryUsage().external - startingMemory, 10000); //100 * 100
var resultImage = originalImage.adaptiveThreshold(255, 0, 0, 15, 2);
t.equal(process.memoryUsage().external - startingMemory, 20000); //our original image plus our new one
t.equal(originalImage.getrefCount(), 1);
t.equal(resultImage.getrefCount(), 1);
originalImage.release();
resultImage.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
test("Matrix meanStdDev", t=>{
gc();
var startingMemory = process.memoryUsage().external;
var matrix = new cv.Matrix.Ones(100, 100, cv.Constants.CV_8UC3);
t.equal(process.memoryUsage().external - startingMemory, 30000);
var result = matrix.meanStdDev();
t.equal(result.mean.getrefCount(), 1);
t.equal(result.stddev.getrefCount(), 1);
t.equal(process.memoryUsage().external - startingMemory, 30048);
matrix.release();
result.mean.release();
result.stddev.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
test("Matrix copyTo", t=>{
gc();
var startingMemory = process.memoryUsage().external;
var smallImg = new cv.Matrix.Ones(25, 25, cv.Constants.CV_8UC3);
var bigImg = cv.Matrix.Zeros(100, 100, cv.Constants.CV_8UC3);
t.equal(process.memoryUsage().external - startingMemory, 30000 + 1875);
smallImg.copyTo(bigImg, 0, 0);
t.equal(smallImg.getrefCount(), 1);
t.equal(bigImg.getrefCount(), 1);
t.equal(process.memoryUsage().external - startingMemory, 30000 + 1875);
smallImg.release();
bigImg.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
test("Matrix cvtColor", t=>{
gc();
var startingMemory = process.memoryUsage().external;
var image = new cv.Matrix(100, 100, cv.Constants.CV_8UC3, [0,0,0]);
t.equal(process.memoryUsage().external - startingMemory, 30000); //100 * 100 * 3
image.cvtColor("CV_BGR2GRAY");
t.equal(process.memoryUsage().external - startingMemory, 10000); //grayscale is smaller
image.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
test("Matrix split", t=>{
gc();
var startingMemory = process.memoryUsage().external;
var image = new cv.Matrix(100, 100, cv.Constants.CV_8UC3, [0,0,0]);
t.equal(process.memoryUsage().external - startingMemory, 30000); //100 * 100 * 3
var result = image.split();
t.equal(process.memoryUsage().external - startingMemory, 60000);
image.release();
t.equal(process.memoryUsage().external - startingMemory, 30000);
result.forEach(m => m.release());
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
test("Matrix merge", t=>{
gc();
var startingMemory = process.memoryUsage().external;
var image1 = new cv.Matrix(100, 100, cv.Constants.CV_8UC1, [0]);
var image2 = new cv.Matrix(100, 100, cv.Constants.CV_8UC1, [0]);
var image3 = new cv.Matrix(100, 100, cv.Constants.CV_8UC1, [0]);
t.equal(process.memoryUsage().external - startingMemory, 30000); //100 * 100 * 3
var result = new cv.Matrix(10,10);
t.equal(process.memoryUsage().external - startingMemory, 30000 + 1200);
result.merge([image1, image2, image3]);
t.equal(process.memoryUsage().external - startingMemory, 60000);
result.release();
image1.release();
image2.release();
image3.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
test("Matrix reshape", t=>{
gc();
var startingMemory = process.memoryUsage().external;
var image = new cv.Matrix(100, 100, cv.Constants.CV_8UC3, [0,0,0]);
t.equal(process.memoryUsage().external - startingMemory, 30000); //100 * 100 * 3
var result = image.reshape(2);
t.equal(process.memoryUsage().external - startingMemory, 30000); //reshape does not copy data, so the allocated size hasn't changed
image.release();
t.equal(process.memoryUsage().external - startingMemory, 30000);
result.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
//********************
// Additional Asynchronous Matrix Functions
//********************
test("Matrix toBuffer async edge case", t=>{
gc();
var startingMemory = process.memoryUsage().external;
var image = new cv.Matrix(1000, 1000, cv.Constants.CV_8UC3, [0,0,0]);
t.equal(process.memoryUsage().external - startingMemory, 3000000); //100 * 100 * 3
image.toBufferAsync((err, buffer)=>{
t.equal(image.getrefCount(), -1); //this happens second, image should have been released already
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 33006); //the size of the buffer, which hasn't been released yet
t.end();
});
image.release();
});
test("Matrix save async edge case", t=>{
gc();
var startingMemory = process.memoryUsage().external;
var image = new cv.Matrix(1000, 1000, cv.Constants.CV_8UC3, [0,0,0]);
t.equal(process.memoryUsage().external - startingMemory, 3000000); //100 * 100 * 3
image.saveAsync(TEMP_SAVE_PATH, (err, buffer)=>{
t.equal(image.getrefCount(), -1); //this happens second, image should have been released already
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
fs.unlinkSync(TEMP_SAVE_PATH);
t.end();
});
image.release();
});
//********************
// Background Subtractors
//********************
function testSyncBackgroundSubtractor(t, subtractor){
gc();
var startingMemory = process.memoryUsage().external;
var image = new cv.Matrix(1000, 1000, cv.Constants.CV_8UC3, [0,0,0]);
t.equal(process.memoryUsage().external - startingMemory, 3000000); //100 * 100 * 3
var output = subtractor.apply(image);
t.equal(image.getrefCount(), 1);
t.equal(output.getrefCount(), 1);
image.release();
output.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
}
function testAsyncBackgroundSubtractor(t, subtractor){
gc();
var startingMemory = process.memoryUsage().external;
var image = new cv.Matrix(1000, 1000, cv.Constants.CV_8UC3, [0,0,0]);
t.equal(process.memoryUsage().external - startingMemory, 3000000); //100 * 100 * 3
subtractor.apply(image, (err, output) => {
t.equal(image.getrefCount(), 1);
t.equal(output.getrefCount(), 1);
image.release();
output.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
}
function testAsyncBackgroundSubtractorEarlyRelease(t, subtractor){
gc();
var startingMemory = process.memoryUsage().external;
var image = new cv.Matrix(1000, 1000, cv.Constants.CV_8UC3, [0,0,0]);
t.equal(process.memoryUsage().external - startingMemory, 3000000); //100 * 100 * 3
subtractor.apply(image, (err, output) => {
t.equal(image.getrefCount(), -1);
t.equal(output.getrefCount(), 1);
output.release();
var endingMemory = process.memoryUsage().external;
t.equal(endingMemory - startingMemory, 0);
t.end();
});
image.release();
}
test("default background subtractor", t=>{
testSyncBackgroundSubtractor(t, new cv.BackgroundSubtractor());
});
test("MOG background subtractor", t=>{
testSyncBackgroundSubtractor(t, cv.BackgroundSubtractor.createMOG());
});
test("MOG2 background subtractor", t=>{
testSyncBackgroundSubtractor(t, cv.BackgroundSubtractor.createMOG2());
});
test("GMG background subtractor", t=>{
testSyncBackgroundSubtractor(t, cv.BackgroundSubtractor.createGMG());
});
test("default background subtractor async", t=>{
testAsyncBackgroundSubtractor(t, new cv.BackgroundSubtractor());
});
test("MOG background subtractor async", t=>{
testAsyncBackgroundSubtractor(t, cv.BackgroundSubtractor.createMOG());
});
test("MOG2 background subtractor async", t=>{
testAsyncBackgroundSubtractor(t, cv.BackgroundSubtractor.createMOG2());
});
test("GMG background subtractor async", t=>{
testAsyncBackgroundSubtractor(t, cv.BackgroundSubtractor.createGMG());
});
test("default background subtractor async early release", t=>{
testAsyncBackgroundSubtractorEarlyRelease(t, new cv.BackgroundSubtractor());
});
test("MOG background subtractor async early release", t=>{
testAsyncBackgroundSubtractorEarlyRelease(t, cv.BackgroundSubtractor.createMOG());
});
test("MOG2 background subtractor async early release", t=>{
testAsyncBackgroundSubtractorEarlyRelease(t, cv.BackgroundSubtractor.createMOG2());
});
test("GMG background subtractor async early release", t=>{
testAsyncBackgroundSubtractorEarlyRelease(t, cv.BackgroundSubtractor.createGMG());
});