diff --git a/src/BackgroundSubtractor.cc b/src/BackgroundSubtractor.cc index 0afa37a..15c1a0e 100644 --- a/src/BackgroundSubtractor.cc +++ b/src/BackgroundSubtractor.cc @@ -283,7 +283,7 @@ NAN_METHOD(BackgroundSubtractorWrap::ApplyMOG) { #endif } - Local fgMask = Matrix::CreateWrappedFromMat(_fgMask); + Local 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 im_to_return = Matrix::CreateWrappedFromMat(_fgMask); + delete matrix; + matrix = NULL; + + Local im_to_return = Matrix::CreateWrappedFromMat(_fgMask.clone()); Local 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()); Matrix *_img = Nan::ObjectWrap::Unwrap(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(); diff --git a/src/Matrix.cc b/src/Matrix.cc index 0497001..634cae4 100755 --- a/src/Matrix.cc +++ b/src/Matrix.cc @@ -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 Matrix::CreateWrappedFromMat(cv::Mat mat){ Local < Object > result = Nan::NewInstance(Nan::GetFunction(Nan::New(Matrix::constructor)).ToLocalChecked()).ToLocalChecked(); Matrix *m = Nan::ObjectWrap::Unwrap(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 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(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 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 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 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 vec(0); // std::vector 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 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 params; std::vector res; @@ -869,7 +894,7 @@ NAN_METHOD(Matrix::ToBufferAsync) { } Nan::Callback *callback = new Nan::Callback(cb.As()); - 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 argv[] = { Nan::Null(), Nan::New(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()); - 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(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(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 img_to_return = Nan::NewInstance(Nan::GetFunction(Nan::New(Matrix::constructor)).ToLocalChecked()).ToLocalChecked(); Matrix *img = Nan::ObjectWrap::Unwrap(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 img_to_return = Matrix::CreateWrappedFromMat(roi); + Local img_to_return = Matrix::CreateWrappedFromMatIfNotReferenced(roi, 1); info.GetReturnValue().Set(img_to_return); } @@ -1397,7 +1425,7 @@ NAN_METHOD(Matrix::Dct) { Local out = Nan::NewInstance(Nan::GetFunction(Nan::New(Matrix::constructor)).ToLocalChecked()).ToLocalChecked(); Matrix *m_out = Nan::ObjectWrap::Unwrap(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 out = Nan::NewInstance(Nan::GetFunction(Nan::New(Matrix::constructor)).ToLocalChecked()).ToLocalChecked(); Matrix *m_out = Nan::ObjectWrap::Unwrap(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(info[0]->ToObject()); - Local out = Nan::NewInstance(Nan::GetFunction(Nan::New(Matrix::constructor)).ToLocalChecked()).ToLocalChecked(); - Matrix *m_out = Nan::ObjectWrap::Unwrap(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 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 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 im_to_return = Matrix::CreateWrappedFromMat(dest->mat); - delete dest; - dest = NULL; + Local im_to_return = Matrix::CreateWrappedFromMat(dest); + delete image; + image = NULL; + dest.release(); //release our refcount before handing it back to the callback Local argv[] = { Nan::Null(), // err @@ -1965,8 +1987,6 @@ public: Nan::FatalException(try_catch); } } catch (...){ - delete dest; - dest = NULL; Local argv[] = { Nan::New("C++ exception wrapping response").ToLocalChecked(), // err Nan::Null() // result @@ -1979,9 +1999,6 @@ public: } } } else { - delete dest; - dest = NULL; - Local 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()); - 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(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(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(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(info.This()); - Local mean = Nan::NewInstance(Nan::GetFunction(Nan::New(Matrix::constructor)).ToLocalChecked()).ToLocalChecked(); - Matrix *m_mean = Nan::ObjectWrap::Unwrap(mean); - Local stddev = Nan::NewInstance(Nan::GetFunction(Nan::New(Matrix::constructor)).ToLocalChecked()).ToLocalChecked(); - Matrix *m_stddev = Nan::ObjectWrap::Unwrap(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 data = Nan::New(); - data->Set(Nan::New("mean").ToLocalChecked(), mean); - data->Set(Nan::New("stddev").ToLocalChecked(), stddev); + data->Set(Nan::New("mean").ToLocalChecked(), CreateWrappedFromMat(meanMat)); + data->Set(Nan::New("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 arrChannels = Nan::New(size); for (unsigned int i = 0; i < size; i++) { - Local matObject = Matrix::CreateWrappedFromMat(channels[i]); + Local 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 jsChannels = v8::Local::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 img_to_return = Matrix::CreateWrappedFromMat(self->mat.reshape(cn, rows)); + Local 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(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(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(info.This()); + + int refcount = self->getWrappedRefCount(); info.GetReturnValue().Set(Nan::New(refcount)); return; diff --git a/src/Matrix.h b/src/Matrix.h index 9f69de9..59f8e21 100755 --- a/src/Matrix.h +++ b/src/Matrix.h @@ -8,7 +8,10 @@ public: static void Init(Local target); static NAN_METHOD(New); static Local CreateWrappedFromMat(cv::Mat mat); + static Local 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); diff --git a/src/OpenCV.cc b/src/OpenCV.cc index 7ce38fe..2750996 100755 --- a/src/OpenCV.cc +++ b/src/OpenCV.cc @@ -49,6 +49,7 @@ public: try{ Local im_to_return = Matrix::CreateWrappedFromMat(outputmat); + outputmat.release(); Local argv[] = { Nan::Null(), @@ -110,6 +111,7 @@ public: Nan::HandleScope scope; Local im_to_return = Matrix::CreateWrappedFromMat(outputmat); + outputmat.release(); Local argv[] = { Nan::Null(), @@ -165,7 +167,7 @@ NAN_METHOD(OpenCV::ReadImageAsync) { width = info[0]->Uint32Value(); height = info[1]->Uint32Value(); - Local img_to_return = Matrix::CreateWrappedFromMat(*(new cv::Mat(width, height, type))); + Local 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(); diff --git a/src/VideoCaptureWrap.cc b/src/VideoCaptureWrap.cc index 34e15d5..0e1f4cc 100755 --- a/src/VideoCaptureWrap.cc +++ b/src/VideoCaptureWrap.cc @@ -40,7 +40,7 @@ void VideoCaptureWrap::Init(Local 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 im_to_return = Matrix::CreateWrappedFromMat(mat); + mat.release(); Local argv[] = { Nan::Null() @@ -274,11 +275,10 @@ NAN_METHOD(VideoCaptureWrap::ReadSync) { Nan::HandleScope scope; VideoCaptureWrap *v = Nan::ObjectWrap::Unwrap(info.This()); - Local im_to_return= Nan::NewInstance(Nan::GetFunction(Nan::New(Matrix::constructor)).ToLocalChecked()).ToLocalChecked(); - Matrix *img = Nan::ObjectWrap::Unwrap(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 im_to_return = Matrix::CreateWrappedFromMat(outputmat); info.GetReturnValue().Set(im_to_return); } diff --git a/test/memory.js b/test/memory.js new file mode 100644 index 0000000..1d3abe4 --- /dev/null +++ b/test/memory.js @@ -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()); +});