From 2839bce325ca5a93f02834afb330f909a2afd0e1 Mon Sep 17 00:00:00 2001 From: Pierre Colle Date: Wed, 23 Aug 2017 10:57:09 +0200 Subject: [PATCH 01/12] add calcHist implementation --- binding.gyp | 3 +- examples/calc-hist.js | 27 +++++++++++++++++ src/Histogram.cc | 68 +++++++++++++++++++++++++++++++++++++++++++ src/Histogram.h | 15 ++++++++++ src/init.cc | 2 ++ 5 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 examples/calc-hist.js create mode 100644 src/Histogram.cc create mode 100644 src/Histogram.h diff --git a/binding.gyp b/binding.gyp index 69b4d12..d0e3d17 100755 --- a/binding.gyp +++ b/binding.gyp @@ -20,7 +20,8 @@ "src/Calib3D.cc", "src/ImgProc.cc", "src/Stereo.cc", - "src/LDAWrap.cc" + "src/LDAWrap.cc", + "src/Histogram.cc", ], "libraries": [ diff --git a/examples/calc-hist.js b/examples/calc-hist.js new file mode 100644 index 0000000..bf7c81d --- /dev/null +++ b/examples/calc-hist.js @@ -0,0 +1,27 @@ +var cv = require('../lib/opencv'); + +// (B)lue, (G)reen, (R)ed +var histSize = 256; + +cv.readImage('./files/car1.jpg', function(err, im) { + if (err) throw err; + if (im.width() < 1 || im.height() < 1) throw new Error('Image has no size'); + + var bgrPlanes = im.split(); + + var size = 4, + range = [0, 256], + uniform = true, + accumulate = true, + histFile = 'files/chart.png'; + + + /// Compute the histograms: + var bHist = cv.histogram.calcHist( bgrPlanes[0], size, range, uniform, accumulate ); + var gHist = cv.histogram.calcHist( bgrPlanes[1], size, range, uniform, accumulate ); + var rHist = cv.histogram.calcHist( bgrPlanes[2], size, range, uniform, accumulate ); + + console.log("Histograms are \n Blue : ", bHist, "\n Green : ", gHist, "\n Red : ", rHist); + + +}); diff --git a/src/Histogram.cc b/src/Histogram.cc new file mode 100644 index 0000000..72bbd9d --- /dev/null +++ b/src/Histogram.cc @@ -0,0 +1,68 @@ +#include "Histogram.h" +#include "Matrix.h" + +void Histogram::Init(Local target) { + Nan::Persistent inner; + Local obj = Nan::New(); + inner.Reset(obj); + + Nan::SetMethod(obj, "calcHist", CalcHist); + + target->Set(Nan::New("histogram").ToLocalChecked(), obj); +} + +// cv::distanceTransform +NAN_METHOD(Histogram::CalcHist) { + Nan::EscapableHandleScope scope; + + try { + // Arg 0 is the image + Matrix* m0 = Nan::ObjectWrap::Unwrap(info[0]->ToObject()); + cv::Mat inputImage = m0->mat; + + // Arg 1 is histogram sizes in first dimension + /// Establish the number of bins + int histSize = info[1]->IntegerValue(); + + // Arg 2 is array of the histogram bin boundaries in each dimension + + Local nodeRange = Local::Cast(info[2]->ToObject()); + unsigned int num_range = nodeRange->Length(); + + /// Set the ranges ( for B,G,R) ) + float range[num_range]; + for (unsigned int i = 0; i < num_range; i++) { + range[i] = nodeRange->Get(i)->NumberValue(); + } + const float* histRange = { range }; + + // Arg 3 is uniform flag + bool uniform = info[3]->BooleanValue(); + + // Arg 4 is accumulate flag + bool accumulate = info[4]->BooleanValue(); + + // Make a mat to hold the result image + cv::Mat outputHist; + + // Perform calcHist + cv::calcHist(&inputImage, 1, 0, cv::Mat(), outputHist, 1, &histSize, &histRange, uniform, accumulate); + + // Wrap the output image + //Local outMatrixWrap = Nan::NewInstance(Nan::GetFunction(Nan::New(Matrix::constructor)).ToLocalChecked()).ToLocalChecked(); + //Matrix *outMatrix = Nan::ObjectWrap::Unwrap(outMatrixWrap); + //outMatrix->mat = outputHist; + + v8::Local arr = Nan::New(histSize); + + for (unsigned int i=0; i < (unsigned int) histSize; i++) { + arr->Set(i, Nan::New(outputHist.at(i))); + } + + info.GetReturnValue().Set(arr); + } catch (cv::Exception &e) { + const char *err_msg = e.what(); + Nan::ThrowError(err_msg); + return; + } +} diff --git a/src/Histogram.h b/src/Histogram.h new file mode 100644 index 0000000..f5871c0 --- /dev/null +++ b/src/Histogram.h @@ -0,0 +1,15 @@ +#ifndef __NODE_HISTOGRAM_H +#define __NODE_HISTOGRAM_H + +#include "OpenCV.h" + +/** + * Implementation of histogram.hpp functions + */ +class Histogram: public Nan::ObjectWrap { +public: + static void Init(Local target); + static NAN_METHOD(CalcHist); +}; + +#endif diff --git a/src/init.cc b/src/init.cc index 7d91bbe..10f4d76 100755 --- a/src/init.cc +++ b/src/init.cc @@ -15,6 +15,7 @@ #include "Stereo.h" #include "BackgroundSubtractor.h" #include "LDAWrap.h" +#include "Histogram.h" extern "C" void init(Local target) { Nan::HandleScope scope; @@ -30,6 +31,7 @@ extern "C" void init(Local target) { Constants::Init(target); Calib3D::Init(target); ImgProc::Init(target); + Histogram::Init(target); #if CV_MAJOR_VERSION < 3 StereoBM::Init(target); StereoSGBM::Init(target); From 1bb61fc38a3b143201b1b2507f9bb58f43f303d1 Mon Sep 17 00:00:00 2001 From: Pierre Colle Date: Wed, 23 Aug 2017 19:14:32 +0200 Subject: [PATCH 02/12] Histogram now takes multiple dimensions --- examples/calc-hist.js | 54 ++++++++++++++++++--- src/Histogram.cc | 109 ++++++++++++++++++++++++++++++++---------- 2 files changed, 131 insertions(+), 32 deletions(-) diff --git a/examples/calc-hist.js b/examples/calc-hist.js index bf7c81d..9025aff 100644 --- a/examples/calc-hist.js +++ b/examples/calc-hist.js @@ -9,19 +9,59 @@ cv.readImage('./files/car1.jpg', function(err, im) { var bgrPlanes = im.split(); - var size = 4, + var size = 256, range = [0, 256], uniform = true, accumulate = true, - histFile = 'files/chart.png'; + histFile = 'files/chart2.png'; /// Compute the histograms: - var bHist = cv.histogram.calcHist( bgrPlanes[0], size, range, uniform, accumulate ); - var gHist = cv.histogram.calcHist( bgrPlanes[1], size, range, uniform, accumulate ); - var rHist = cv.histogram.calcHist( bgrPlanes[2], size, range, uniform, accumulate ); - - console.log("Histograms are \n Blue : ", bHist, "\n Green : ", gHist, "\n Red : ", rHist); + var hist64 = cv.histogram.calcHist( im, [0, 1, 2], [4, 4, 4], [[0, 256], [0, 256], [0, 256]], uniform, accumulate ); + var bHist = cv.histogram.calcHist( im, [0], [size], [range], uniform, accumulate ); + var gHist = cv.histogram.calcHist( im, [1], [size], [range], uniform, accumulate ); + var rHist = cv.histogram.calcHist( im, [2], [size], [range], uniform, accumulate ); +////// +// Uncommentand run `npm install chartjs-node` to draw the histogram ! +/// +/* + var ChartjsNode = require('chartjs-node'); + var chartNode = new ChartjsNode(1200, 1200); + chartNode.drawChart({ + type: 'bar', + data: { + labels: bHist.map(function(a,i){return i.toString()}), + datasets : [{ + data : bHist, + backgroundColor : "#4183c4", + borderColor : "#0c4b8a", + label : 'Blue' + },{ + data : gHist, + backgroundColor : "#83c441", + borderColor : "#0c4b8a", + label : 'Green' + },{ + data : rHist, + backgroundColor : "#c44183", + borderColor : "#0c4b8a", + label : 'Red' + }] + }, + options: { + title: { + display: true, + text: 'RGB Histograms' + } + } + }).then(function(){ + return chartNode.writeImageToFile('image/png', histFile); + }).then(function(){ + console.log("result has been written in "+histFile) + }).catch(function(e){ + console.log("error",e) + }); +*/ }); diff --git a/src/Histogram.cc b/src/Histogram.cc index 72bbd9d..6c1ad97 100644 --- a/src/Histogram.cc +++ b/src/Histogram.cc @@ -20,43 +20,102 @@ NAN_METHOD(Histogram::CalcHist) { Matrix* m0 = Nan::ObjectWrap::Unwrap(info[0]->ToObject()); cv::Mat inputImage = m0->mat; - // Arg 1 is histogram sizes in first dimension - /// Establish the number of bins - int histSize = info[1]->IntegerValue(); - - // Arg 2 is array of the histogram bin boundaries in each dimension - - Local nodeRange = Local::Cast(info[2]->ToObject()); - unsigned int num_range = nodeRange->Length(); - - /// Set the ranges ( for B,G,R) ) - float range[num_range]; - for (unsigned int i = 0; i < num_range; i++) { - range[i] = nodeRange->Get(i)->NumberValue(); + //int dims = 3; + // Arg 1 is the channel + Local nodeChannels = Local::Cast(info[1]->ToObject()); + const unsigned int dims = nodeChannels->Length(); + int channels[dims]; + for (unsigned int i = 0; i < dims; i++) { + channels[i] = nodeChannels->Get(i)->IntegerValue(); } - const float* histRange = { range }; - // Arg 3 is uniform flag - bool uniform = info[3]->BooleanValue(); + //int channels[] = {0, 1, 2}; - // Arg 4 is accumulate flag - bool accumulate = info[4]->BooleanValue(); + // Arg 2 is histogram sizes in each dimension + Local nodeHistSizes = Local::Cast(info[2]->ToObject()); + int histSize[dims]; + for (unsigned int i = 0; i < dims; i++) { + histSize[i] = nodeHistSizes->Get(i)->IntegerValue(); + } + // Arg 3 is array of the histogram bin boundaries in each dimension + + Local nodeRanges = Local::Cast(info[3]->ToObject()); + /// Set the ranges ( for B,G,R) ) + float histRanges[dims][2]; + + for (unsigned int i = 0; i < dims; i++) { + Local nodeRange = Local::Cast(nodeRanges->Get(i)->ToObject()); + float lower = nodeRange->Get(0)->NumberValue(); + float higher = nodeRange->Get(1)->NumberValue(); + histRanges[i][0] = lower; + histRanges[i][1] = higher; + } + + float first_range[] = { histRanges[0][0], histRanges[0][1] }; + float second_range[] = { 0, 0}; + float third_range[] = { 0, 0}; + + if(dims >= 2){ + second_range[0] = histRanges[1][0]; + second_range[1] = histRanges[1][1]; + } + if(dims >= 3){ + third_range[0] = histRanges[2][0]; + third_range[1] = histRanges[2][1]; + } + + const float* histRanges1[] = {first_range, second_range, third_range}; + + // Arg 4 is uniform flag + bool uniform = info[4]->BooleanValue(); + + // Arg 5 is accumulate flag + bool accumulate = info[5]->BooleanValue(); +/* + float rranges[] = { 0, 256 }; + float granges[] = { 0, 256 }; + float branges[] = { 0, 256 }; + + const float* histRange[] = { rranges, granges, branges}; +*/ // Make a mat to hold the result image cv::Mat outputHist; // Perform calcHist - cv::calcHist(&inputImage, 1, 0, cv::Mat(), outputHist, 1, &histSize, &histRange, uniform, accumulate); + cv::calcHist(&inputImage, 1, channels, cv::Mat(), outputHist, dims, histSize, histRanges1, uniform, accumulate); // Wrap the output image - //Local outMatrixWrap = Nan::NewInstance(Nan::GetFunction(Nan::New(Matrix::constructor)).ToLocalChecked()).ToLocalChecked(); - //Matrix *outMatrix = Nan::ObjectWrap::Unwrap(outMatrixWrap); - //outMatrix->mat = outputHist; + /*Local outMatrixWrap = Nan::NewInstance(Nan::GetFunction(Nan::New(Matrix::constructor)).ToLocalChecked()).ToLocalChecked(); + Matrix *outMatrix = Nan::ObjectWrap::Unwrap(outMatrixWrap); + outMatrix->mat = outputHist; - v8::Local arr = Nan::New(histSize); + info.GetReturnValue().Set(outMatrixWrap);*/ - for (unsigned int i=0; i < (unsigned int) histSize; i++) { - arr->Set(i, Nan::New(outputHist.at(i))); + v8::Local arr = Nan::New(histSize[0]); + + if(dims < 1 || dims > 3){ + return Nan::ThrowTypeError("OPENCV nodejs binding error : only dimensions from 1 to 3 are allowed"); + } + + for (unsigned int i=0; i < (unsigned int) histSize[0]; i++) { + if(dims <= 1){ + arr->Set(i, Nan::New(outputHist.at(i))); + } else { + v8::Local arr2 = Nan::New(dims); + for (unsigned int j=0; j < (unsigned int) histSize[1]; j++) { + if(dims <= 2){ + arr2->Set(j, Nan::New(outputHist.at(i,j))); + } else { + v8::Local arr3 = Nan::New(dims); + for (unsigned int k=0; k < (unsigned int) histSize[1]; k++) { + arr3->Set(k, Nan::New(outputHist.at(i,j,k))); + } + arr2->Set(j, arr3); + } + } + arr->Set(i, arr2); + } } info.GetReturnValue().Set(arr); From 5a9a75f829282f3c8de2b52412a1d8458d07881e Mon Sep 17 00:00:00 2001 From: Pierre Colle Date: Wed, 23 Aug 2017 20:15:18 +0200 Subject: [PATCH 03/12] adding emd --- examples/emd.js | 72 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 examples/emd.js diff --git a/examples/emd.js b/examples/emd.js new file mode 100644 index 0000000..5e711b8 --- /dev/null +++ b/examples/emd.js @@ -0,0 +1,72 @@ +var cv = require('../lib/opencv'); + +// (B)lue, (G)reen, (R)ed +var histSize = 256; + +cv.readImage('./files/car1.jpg', function(err, im1) { + if (err) throw err; + if (im.width() < 1 || im.height() < 1) throw new Error('Image has no size'); + cv.readImage('./files/car2.jpg', function(err, im2) { + + var bgrPlanes = im.split(); + + var size = [4, 4, 4], + channels = [0, 1, 2], + range = [[0, 256], [0, 256], [0, 256]], + uniform = true, + accumulate = true, + histFile = 'files/chart2.png'; + + + /// Compute 64 (=4^3) histograms: + + var firstImageHist64 = cv.histogram.calcHist(im1, channels, size, range, uniform, accumulate); + var secondImageHist64 = cv.histogram.calcHist(im2, channels, size, range, uniform, accumulate); + + var sig1 = flatten(firstImageHist64); + var sig2 = flatten(secondImageHist64); + + var step = 256/4; + var halfStep = Math.round(step/2) + + var middles = flatten(firstImageHist64.map(function(bHist, bIndex){ + return blueValue.map(function(bgHist, gIndex){ + return greenValue.map(function(bgrHist, rIndex){ + return [ + bIndex*step + halfStep, + gIndex*step + halfStep, + rIndex*step + halfStep + ];/*{ + b : bIndex*step + halfStep, + g : gIndex*step + halfStep, + r : rIndex*step + halfStep + }*/ + }) + }) + })); + + var mat = new opencv.Matrix(64,1,cv.Constants.CV_8UC3); + var buf = Buffer(64*1*3); + buf.fill(0); + for(var i=0;i<64*1*3;i++){ + buf[i] = middles[i]; + } + mat.put(buf); + mat.cvtColor("CV_BGR2Luv"); + + var luvValues = mat.toArray()[0]; + + var distance = function(luv1, luv2){ + return Math.sqrt((luv1[0]-luv2[0])*(luv1[0]-luv2[0]) + (luv1[1]-luv2[1])*(luv1[1]-luv2[1]) + (luv1[2]-luv2[2])*(luv1[2]-luv2[2])); + }; + + var costs = luvValues.map(function(luvMiddle1){ + return middles.map(function(luvMiddle2){ + return distance(luvMiddle1, luvMiddle2); + }) + }) + + emd(sig1, sig2, cv.Constants.CV_DIST_USER, cost); + + }); +}); From 845643157eaff27f97c9d8ba5f4693dc395a368a Mon Sep 17 00:00:00 2001 From: Pierre Colle Date: Thu, 24 Aug 2017 19:40:32 +0200 Subject: [PATCH 04/12] adding fromArray, toArray, EMD --- examples/calc-hist.js | 7 +- examples/emd.js | 163 ++++++++++++++++++++++++------- examples/mat-array-conversion.js | 20 ++++ lib/opencv.js | 120 ++++++++++++++++++++++- src/Constants.cc | 1 + src/Histogram.cc | 74 +++++++++++--- src/Histogram.h | 1 + 7 files changed, 330 insertions(+), 56 deletions(-) create mode 100644 examples/mat-array-conversion.js diff --git a/examples/calc-hist.js b/examples/calc-hist.js index 9025aff..2931809 100644 --- a/examples/calc-hist.js +++ b/examples/calc-hist.js @@ -12,16 +12,15 @@ cv.readImage('./files/car1.jpg', function(err, im) { var size = 256, range = [0, 256], uniform = true, - accumulate = true, histFile = 'files/chart2.png'; /// Compute the histograms: var hist64 = cv.histogram.calcHist( im, [0, 1, 2], [4, 4, 4], [[0, 256], [0, 256], [0, 256]], uniform, accumulate ); - var bHist = cv.histogram.calcHist( im, [0], [size], [range], uniform, accumulate ); - var gHist = cv.histogram.calcHist( im, [1], [size], [range], uniform, accumulate ); - var rHist = cv.histogram.calcHist( im, [2], [size], [range], uniform, accumulate ); + var bHist = cv.histogram.calcHist( im, [0], [size], [range], uniform); + var gHist = cv.histogram.calcHist( im, [1], [size], [range], uniform); + var rHist = cv.histogram.calcHist( im, [2], [size], [range], uniform); ////// // Uncommentand run `npm install chartjs-node` to draw the histogram ! diff --git a/examples/emd.js b/examples/emd.js index 5e711b8..e13685c 100644 --- a/examples/emd.js +++ b/examples/emd.js @@ -1,14 +1,39 @@ var cv = require('../lib/opencv'); -// (B)lue, (G)reen, (R)ed -var histSize = 256; +// +// Example of use of EMD distance using histograms +// 1. Build 2 histograms from images using calcHist +// 2. Transform each histogram to a 64 x 4 (hist, b, g, r) x 1 normalized signatures in BGR space +// 3. Compute the cost matrix (64 x 64 x 1), calculating the cost in LUV space +// 4. Run EMD algorithm +// + +/// Useful flatten function for step 2 + +function flatten(array, accu) { + if(!accu){ + accu = []; + } + array.forEach(function(a){ + if(Array.isArray(a)) { + flatten(a, accu) + } else { + accu.push(a) + } + }); + return accu +}; cv.readImage('./files/car1.jpg', function(err, im1) { if (err) throw err; - if (im.width() < 1 || im.height() < 1) throw new Error('Image has no size'); + if (im1.width() < 1 || im1.height() < 1) throw new Error('Image has no size'); cv.readImage('./files/car2.jpg', function(err, im2) { + if (err) throw err; + if (im2.width() < 1 || im2.height() < 1) throw new Error('Image has no size'); - var bgrPlanes = im.split(); + /////////////////// + // 1. Build 2 histograms from images using calcHist + ////////////////// var size = [4, 4, 4], channels = [0, 1, 2], @@ -17,56 +42,120 @@ cv.readImage('./files/car1.jpg', function(err, im1) { accumulate = true, histFile = 'files/chart2.png'; - /// Compute 64 (=4^3) histograms: + var firstImageHist64 = cv.histogram.calcHist(im1, channels, size, range, uniform); + var secondImageHist64 = cv.histogram.calcHist(im2, channels, size, range, uniform); - var firstImageHist64 = cv.histogram.calcHist(im1, channels, size, range, uniform, accumulate); - var secondImageHist64 = cv.histogram.calcHist(im2, channels, size, range, uniform, accumulate); - - var sig1 = flatten(firstImageHist64); - var sig2 = flatten(secondImageHist64); + ////////////// + // 2. Transform each histogram to a 64 x 4 (hist, b, g, r) x 1 normalized signatures in BGR space + //////////////// var step = 256/4; - var halfStep = Math.round(step/2) + var halfStep = Math.round(step/2); - var middles = flatten(firstImageHist64.map(function(bHist, bIndex){ - return blueValue.map(function(bgHist, gIndex){ - return greenValue.map(function(bgrHist, rIndex){ - return [ - bIndex*step + halfStep, - gIndex*step + halfStep, - rIndex*step + halfStep - ];/*{ - b : bIndex*step + halfStep, - g : gIndex*step + halfStep, - r : rIndex*step + halfStep - }*/ + var sum1 = 0; + var sum2 = 0; + + firstImageHist64.map(function(bHist, bIndex){ + return bHist.map(function(bgHist, gIndex){ + return bgHist.map(function(bgrHist, rIndex){ + sum1 += bgrHist; }) }) - })); + }) - var mat = new opencv.Matrix(64,1,cv.Constants.CV_8UC3); - var buf = Buffer(64*1*3); - buf.fill(0); - for(var i=0;i<64*1*3;i++){ - buf[i] = middles[i]; - } - mat.put(buf); + var sig1 = flatten(firstImageHist64.map(function(bHist, bIndex){ + return bHist.map(function(bgHist, gIndex){ + return bgHist.map(function(bgrHist, rIndex){ + return { + data :[ + [bgrHist/sum1], + [bIndex*step + halfStep], + [gIndex*step + halfStep], + [rIndex*step + halfStep] + ] + } + }) + }) + })).map(function(a){ + // trick to avoid flattening and get a 64 x 4 x 1 image as needed + return a.data; + }); + + secondImageHist64.map(function(bHist, bIndex){ + return bHist.map(function(bgHist, gIndex){ + return bgHist.map(function(bgrHist, rIndex){ + sum2 += bgrHist; + }) + }) + }) + + var sig2 = flatten(secondImageHist64.map(function(bHist, bIndex){ + return bHist.map(function(bgHist, gIndex){ + return bgHist.map(function(bgrHist, rIndex){ + return { + data : [ + [bgrHist/sum2], + [bIndex*step + halfStep], + [gIndex*step + halfStep], + [rIndex*step + halfStep] + ] + }; + }) + }) + })).map(function(a){ + // trick to avoid flattening and get a 64 x 4 x 1 image as needed + return a.data; + }); + + ///////////// + // 3. Compute the cost matrix (64 x 64 x 1), calculating the cost in LUV space + ///////////// + + //middles is a 1 x 64 x 3 array of the middles positions in RGB used to change to LUV + var middles = [flatten(firstImageHist64.map(function(bHist, bIndex){ + return bHist.map(function(bgHist, gIndex){ + return bgHist.map(function(bgrHist, rIndex){ + return { + data : [ + bIndex*step + halfStep, + gIndex*step + halfStep, + rIndex*step + halfStep + ] + } + }) + }) + })).map(function(a){ + // trick to avoid flattening and get a 1 x 64 x 3 image as needed + return a.data; + })]; + + var mat = cv.Matrix.fromArray(middles, cv.Constants.CV_8UC3); mat.cvtColor("CV_BGR2Luv"); - var luvValues = mat.toArray()[0]; + //luvValues is a 1 x 64 x 3 array of the middles positions in LUV + var luvMiddles = mat.toArray(); var distance = function(luv1, luv2){ return Math.sqrt((luv1[0]-luv2[0])*(luv1[0]-luv2[0]) + (luv1[1]-luv2[1])*(luv1[1]-luv2[1]) + (luv1[2]-luv2[2])*(luv1[2]-luv2[2])); }; - var costs = luvValues.map(function(luvMiddle1){ - return middles.map(function(luvMiddle2){ - return distance(luvMiddle1, luvMiddle2); + var costs = luvMiddles[0].map(function(luvMiddle1){ + return luvMiddles[0].map(function(luvMiddle2){ + return [distance(luvMiddle1, luvMiddle2)]; }) - }) + }); - emd(sig1, sig2, cv.Constants.CV_DIST_USER, cost); + ////// + // 4. Run EMD algorithm + ///// + var matCosts = cv.Matrix.fromArray(costs, cv.Constants.CV_32FC1); + var matSig1 = cv.Matrix.fromArray(sig1, cv.Constants.CV_32FC1); + var matSig2 = cv.Matrix.fromArray(sig2, cv.Constants.CV_32FC1); + + var dist = cv.Constants.CV_DIST_L2; + var emd = cv.histogram.emd(matSig1, matSig2, dist);//, matCosts); + console.log("EMD is ", emd) }); }); diff --git a/examples/mat-array-conversion.js b/examples/mat-array-conversion.js new file mode 100644 index 0000000..ee70afa --- /dev/null +++ b/examples/mat-array-conversion.js @@ -0,0 +1,20 @@ +var cv = require('../lib/opencv'); + +cv.readImage("./files/mona.png", function(err, orig) { + if (err) throw err; + + var a = orig.toArray(); + var type = orig.type(); + var doubleConversion = cv.Matrix.fromArray(a, type).toArray(); + + for(var i = 0 ; i < a.length; i++){ + for(var j = 0 ; j < a[i].length; j++){ + for(var k = 0 ; k < a[i][j].length; k++){ + if(a[i][j][k] !== doubleConversion[i][j][k]){ + throw(new Error("double conversion is not equal to original")); + } + } + } + } + +}); diff --git a/lib/opencv.js b/lib/opencv.js index dc043a0..283dc8a 100755 --- a/lib/opencv.js +++ b/lib/opencv.js @@ -1,7 +1,8 @@ var Stream = require('stream').Stream , Buffers = require('buffers') , util = require('util') - , path = require('path'); + , path = require('path') + , os = require('os'); var cv = module.exports = require('./bindings'); @@ -33,6 +34,120 @@ Matrix.prototype.inspect = function(){ return "[ Matrix " + size + " ]"; } +// we use the Opencv constants naming convention to extract the number of bytes (8, 16, 32, 64), and the number of channels from constants names +var getNumberOfBytesAndChannelsPerType = function(type){ + var regExp = /CV_([0-9]+)([A-Z]+)([0-9]+)/; + for(var k in cv.Constants) if(cv.Constants.hasOwnProperty(k) && k.match(regExp) && cv.Constants[k] === type){ + var bytes, channels, dataType; + k.replace(regExp, function(all, b, l, c){ + bytes = b; + channels = c; + dataType = l[0] + }); + + return { + bytes : parseInt(bytes), + channels : !isNaN(parseInt(channels)) && parseInt(channels), + dataType : dataType, + label : k + }; + } +}; + +var getBufferMethodName = function(bytes, dataType, endianness, read){ + var fnName; + + if(read){ + fnName = "read"; + } else { + fnName = "write"; + } + + if (bytes === 32 && (dataType === "F" || dataType === "S")){ + if(dataType === "F"){ + fnName += "Float"+endianness; + } else {//dataType === "S" + fnName += "Int32"+endianness; + } + } else if(bytes === 8){ + fnName += (dataType === "U" ? "U" : "")+"Int8"; + } else if(bytes === 16){ + fnName += (dataType === "U" ? "U" : "")+"Int16"+endianness; + } else { + throw("This matrix type ("+type+") is not compatible with fromArray, you can implement if you need it") + } + return fnName; +}; + +Matrix.fromArray = function(arr, type){ + var n_bytes; + + var bytesAndChannels = getNumberOfBytesAndChannelsPerType(type); + var bytes = bytesAndChannels.bytes; + var channels = bytesAndChannels.channels; + var dataType = bytesAndChannels.dataType; + var label = bytesAndChannels.label; + + if(!Array.isArray(arr) ||!Array.isArray(arr[0]) || !Array.isArray(arr[0][0]) || (channels && arr[0][0].length !== channels)) { + throw(new Error("Input array must be a 3-level array/matrix with size rows x cols x channels corresponding to dataType ("+label+")")); + } + + if(!channels){ + channels = 1; + } + + var rows = arr.length; + var cols = arr[0].length; + + var mat = new cv.Matrix(rows, cols, type); + + var n_bytes = bytes/8; + var buf = new Buffer(rows * cols * channels * n_bytes); + + buf.fill(0); + + var fnName = getBufferMethodName(bytes, dataType, os.endianness(), false) + + for(var i=0;i target) { CONST_INT(CV_DIST_C); CONST_INT(CV_DIST_L1); CONST_INT(CV_DIST_L2); + CONST_INT(CV_DIST_USER); CONST_INT(CV_DIST_MASK_3); CONST_INT(CV_DIST_MASK_5); diff --git a/src/Histogram.cc b/src/Histogram.cc index 6c1ad97..5a413fd 100644 --- a/src/Histogram.cc +++ b/src/Histogram.cc @@ -7,11 +7,11 @@ void Histogram::Init(Local target) { inner.Reset(obj); Nan::SetMethod(obj, "calcHist", CalcHist); + Nan::SetMethod(obj, "emd", Emd); target->Set(Nan::New("histogram").ToLocalChecked(), obj); } -// cv::distanceTransform NAN_METHOD(Histogram::CalcHist) { Nan::EscapableHandleScope scope; @@ -67,23 +67,17 @@ NAN_METHOD(Histogram::CalcHist) { const float* histRanges1[] = {first_range, second_range, third_range}; + + //const float** histRanges1 = const_cast(histRanges); + // Arg 4 is uniform flag bool uniform = info[4]->BooleanValue(); - // Arg 5 is accumulate flag - bool accumulate = info[5]->BooleanValue(); -/* - float rranges[] = { 0, 256 }; - float granges[] = { 0, 256 }; - float branges[] = { 0, 256 }; - - const float* histRange[] = { rranges, granges, branges}; -*/ // Make a mat to hold the result image cv::Mat outputHist; // Perform calcHist - cv::calcHist(&inputImage, 1, channels, cv::Mat(), outputHist, dims, histSize, histRanges1, uniform, accumulate); + cv::calcHist(&inputImage, 1, channels, cv::Mat(), outputHist, dims, histSize, histRanges1, uniform); // Wrap the output image /*Local outMatrixWrap = Nan::NewInstance(Nan::GetFunction(Nan::New(Matrix::constructor)).ToLocalChecked()).ToLocalChecked(); @@ -125,3 +119,61 @@ NAN_METHOD(Histogram::CalcHist) { return; } } + +std::vector> nodeArrayToVec(Local input){ + std::vector> ret; + Local nodeMatrix = Local::Cast(input); + const unsigned int size = nodeMatrix->Length(); + for (unsigned int i = 0; i < size; i++) { + Local nodeRow = Local::Cast(nodeMatrix->Get(i)->ToObject()); + std::vector row; + const unsigned int size2 = nodeRow->Length(); + for (unsigned int j = 0; j < size2; j++) { + row.push_back(nodeRow->Get(j)->NumberValue()); + } + ret.push_back(row); + } + + return ret; +} + +// cv::distanceTransform +NAN_METHOD(Histogram::Emd) { + Nan::EscapableHandleScope scope; + + try { + // Arg 0 is the first signature + //std::vector> sig1 = nodeArrayToVec(info[0]->ToObject()); + Matrix* m0 = Nan::ObjectWrap::Unwrap(info[0]->ToObject()); + cv::Mat sig1 = m0->mat; + + // Arg 1 is the second signature + //std::vector> sig2 = nodeArrayToVec(info[1]->ToObject()); + Matrix* m1 = Nan::ObjectWrap::Unwrap(info[1]->ToObject()); + cv::Mat sig2 = m1->mat; + + // Arg 2 is the distance type + int distType = info[2]->IntegerValue(); + + float emd; + + // Arg 3 is the cost matrix + if (info.Length() > 3) { + Matrix* m3 = Nan::ObjectWrap::Unwrap(info[3]->ToObject()); + cv::Mat costs = m3->mat; + + emd = cv::EMD(sig1, sig2, distType, costs); + info.GetReturnValue().Set(emd); + } else { + emd = cv::EMD(sig1, sig2, distType); + } + + //printf("similarity %5.5f %%\n, DistanceType is %i\n", (1-emd)*100, distType); + info.GetReturnValue().Set(emd); + + } catch (cv::Exception &e) { + const char *err_msg = e.what(); + Nan::ThrowError(err_msg); + return; + } +} diff --git a/src/Histogram.h b/src/Histogram.h index f5871c0..d2cf20c 100644 --- a/src/Histogram.h +++ b/src/Histogram.h @@ -10,6 +10,7 @@ class Histogram: public Nan::ObjectWrap { public: static void Init(Local target); static NAN_METHOD(CalcHist); + static NAN_METHOD(Emd); }; #endif From d2566dd492a19e282f3ce8a8150fd95d785d8220 Mon Sep 17 00:00:00 2001 From: Pierre Colle Date: Thu, 24 Aug 2017 20:24:46 +0200 Subject: [PATCH 05/12] fix accumulate issue --- examples/calc-hist.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/calc-hist.js b/examples/calc-hist.js index 2931809..2e0c38c 100644 --- a/examples/calc-hist.js +++ b/examples/calc-hist.js @@ -15,9 +15,10 @@ cv.readImage('./files/car1.jpg', function(err, im) { histFile = 'files/chart2.png'; - /// Compute the histograms: - var hist64 = cv.histogram.calcHist( im, [0, 1, 2], [4, 4, 4], [[0, 256], [0, 256], [0, 256]], uniform, accumulate ); + /// Compute a 3 dimension histogram + var hist64 = cv.histogram.calcHist( im, [0, 1, 2], [4, 4, 4], [[0, 256], [0, 256], [0, 256]], uniform); + /// Compute 3 histograms var bHist = cv.histogram.calcHist( im, [0], [size], [range], uniform); var gHist = cv.histogram.calcHist( im, [1], [size], [range], uniform); var rHist = cv.histogram.calcHist( im, [2], [size], [range], uniform); From fc5aa6bcc3c972e9d39508d50db177ca5454acc5 Mon Sep 17 00:00:00 2001 From: Pierre Colle Date: Thu, 24 Aug 2017 21:51:52 +0200 Subject: [PATCH 06/12] add unit test for coverage --- test/unit.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/unit.js b/test/unit.js index 77adf18..6335242 100755 --- a/test/unit.js +++ b/test/unit.js @@ -449,5 +449,24 @@ test('setColor works will alpha channels', function(assert) { }); }); +test('toArray/fromArray working in both ways', function(assert) { + var cv = require('../lib/opencv'); + + cv.readImage("./examples/files/mona.png", function(err, orig) { + if (err) throw err; + + var a = orig.toArray(); + var type = orig.type(); + var doubleConversion = cv.Matrix.fromArray(a, type).toArray(); + + var randomI = Math.floor(Math.random()*a.length) + var randomJ = Math.floor(Math.random()*a[randomI].length) + var randomK = Math.floor(Math.random()*a[randomI][randomJ].length) + + assert.equal(a[randomI][randomJ][randomK], doubleConversion[randomI][randomJ][randomK]); + assert.end(); + }); +}); + // Test the examples folder. require('./examples')() From 47080c189511febad068757f21e43e3f67382977 Mon Sep 17 00:00:00 2001 From: Pierre Colle Date: Fri, 25 Aug 2017 13:40:03 +0200 Subject: [PATCH 07/12] cleanup code following https://stackoverflow.com/questions/45868943/convert-node-array-variable-lenght-to-a-const-float-to-call-opencv-calchist/45870887#45870887 --- src/Histogram.cc | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/src/Histogram.cc b/src/Histogram.cc index 5a413fd..5faf297 100644 --- a/src/Histogram.cc +++ b/src/Histogram.cc @@ -43,6 +43,7 @@ NAN_METHOD(Histogram::CalcHist) { Local nodeRanges = Local::Cast(info[3]->ToObject()); /// Set the ranges ( for B,G,R) ) float histRanges[dims][2]; + const float* ranges[dims]; for (unsigned int i = 0; i < dims; i++) { Local nodeRange = Local::Cast(nodeRanges->Get(i)->ToObject()); @@ -50,24 +51,9 @@ NAN_METHOD(Histogram::CalcHist) { float higher = nodeRange->Get(1)->NumberValue(); histRanges[i][0] = lower; histRanges[i][1] = higher; + ranges[i] = histRanges[i]; } - float first_range[] = { histRanges[0][0], histRanges[0][1] }; - float second_range[] = { 0, 0}; - float third_range[] = { 0, 0}; - - if(dims >= 2){ - second_range[0] = histRanges[1][0]; - second_range[1] = histRanges[1][1]; - } - if(dims >= 3){ - third_range[0] = histRanges[2][0]; - third_range[1] = histRanges[2][1]; - } - - const float* histRanges1[] = {first_range, second_range, third_range}; - - //const float** histRanges1 = const_cast(histRanges); // Arg 4 is uniform flag @@ -77,7 +63,7 @@ NAN_METHOD(Histogram::CalcHist) { cv::Mat outputHist; // Perform calcHist - cv::calcHist(&inputImage, 1, channels, cv::Mat(), outputHist, dims, histSize, histRanges1, uniform); + cv::calcHist(&inputImage, 1, channels, cv::Mat(), outputHist, dims, histSize, ranges, uniform); // Wrap the output image /*Local outMatrixWrap = Nan::NewInstance(Nan::GetFunction(Nan::New(Matrix::constructor)).ToLocalChecked()).ToLocalChecked(); From 431f9015d837d45363ffa0c781716d87df6b1ac4 Mon Sep 17 00:00:00 2001 From: Pierre Colle Date: Fri, 25 Aug 2017 13:40:14 +0200 Subject: [PATCH 08/12] cleanup code following https://stackoverflow.com/questions/45868943/convert-node-array-variable-lenght-to-a-const-float-to-call-opencv-calchist/45870887#45870887 --- src/Histogram.cc | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/Histogram.cc b/src/Histogram.cc index 5faf297..77e774a 100644 --- a/src/Histogram.cc +++ b/src/Histogram.cc @@ -20,7 +20,6 @@ NAN_METHOD(Histogram::CalcHist) { Matrix* m0 = Nan::ObjectWrap::Unwrap(info[0]->ToObject()); cv::Mat inputImage = m0->mat; - //int dims = 3; // Arg 1 is the channel Local nodeChannels = Local::Cast(info[1]->ToObject()); const unsigned int dims = nodeChannels->Length(); @@ -29,8 +28,6 @@ NAN_METHOD(Histogram::CalcHist) { channels[i] = nodeChannels->Get(i)->IntegerValue(); } - //int channels[] = {0, 1, 2}; - // Arg 2 is histogram sizes in each dimension Local nodeHistSizes = Local::Cast(info[2]->ToObject()); int histSize[dims]; @@ -39,7 +36,6 @@ NAN_METHOD(Histogram::CalcHist) { } // Arg 3 is array of the histogram bin boundaries in each dimension - Local nodeRanges = Local::Cast(info[3]->ToObject()); /// Set the ranges ( for B,G,R) ) float histRanges[dims][2]; @@ -54,8 +50,6 @@ NAN_METHOD(Histogram::CalcHist) { ranges[i] = histRanges[i]; } - //const float** histRanges1 = const_cast(histRanges); - // Arg 4 is uniform flag bool uniform = info[4]->BooleanValue(); @@ -65,13 +59,6 @@ NAN_METHOD(Histogram::CalcHist) { // Perform calcHist cv::calcHist(&inputImage, 1, channels, cv::Mat(), outputHist, dims, histSize, ranges, uniform); - // Wrap the output image - /*Local outMatrixWrap = Nan::NewInstance(Nan::GetFunction(Nan::New(Matrix::constructor)).ToLocalChecked()).ToLocalChecked(); - Matrix *outMatrix = Nan::ObjectWrap::Unwrap(outMatrixWrap); - outMatrix->mat = outputHist; - - info.GetReturnValue().Set(outMatrixWrap);*/ - v8::Local arr = Nan::New(histSize[0]); if(dims < 1 || dims > 3){ From 688d7214651f7eea22808c8e8e36f3c80c3e71b3 Mon Sep 17 00:00:00 2001 From: Pierre Colle Date: Mon, 28 Aug 2017 17:07:33 +0200 Subject: [PATCH 09/12] rm useless function nodeArrayToVec --- src/Histogram.cc | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/Histogram.cc b/src/Histogram.cc index 77e774a..6b2caa5 100644 --- a/src/Histogram.cc +++ b/src/Histogram.cc @@ -93,23 +93,6 @@ NAN_METHOD(Histogram::CalcHist) { } } -std::vector> nodeArrayToVec(Local input){ - std::vector> ret; - Local nodeMatrix = Local::Cast(input); - const unsigned int size = nodeMatrix->Length(); - for (unsigned int i = 0; i < size; i++) { - Local nodeRow = Local::Cast(nodeMatrix->Get(i)->ToObject()); - std::vector row; - const unsigned int size2 = nodeRow->Length(); - for (unsigned int j = 0; j < size2; j++) { - row.push_back(nodeRow->Get(j)->NumberValue()); - } - ret.push_back(row); - } - - return ret; -} - // cv::distanceTransform NAN_METHOD(Histogram::Emd) { Nan::EscapableHandleScope scope; From 27543676289971b42fecbea06cc6d8c821e63c4b Mon Sep 17 00:00:00 2001 From: Pierre Colle Date: Thu, 31 Aug 2017 15:53:45 +0200 Subject: [PATCH 10/12] add Matrix.prototype.mul --- src/Matrix.cc | 26 ++++++++++++++++++++++++-- src/Matrix.h | 1 + 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/Matrix.cc b/src/Matrix.cc index 81a6ba3..586f43d 100755 --- a/src/Matrix.cc +++ b/src/Matrix.cc @@ -69,7 +69,7 @@ void Matrix::Init(Local target) { Nan::SetPrototypeMethod(ctor, "dct", Dct); Nan::SetPrototypeMethod(ctor, "idct", Idct); Nan::SetPrototypeMethod(ctor, "addWeighted", AddWeighted); - Nan::SetPrototypeMethod(ctor, "add", Add); + Nan::SetPrototypeMethod(ctor, "add", Add); Nan::SetPrototypeMethod(ctor, "bitwiseXor", BitwiseXor); Nan::SetPrototypeMethod(ctor, "bitwiseNot", BitwiseNot); Nan::SetPrototypeMethod(ctor, "bitwiseAnd", BitwiseAnd); @@ -116,6 +116,7 @@ void Matrix::Init(Local target) { Nan::SetPrototypeMethod(ctor, "reshape", Reshape); Nan::SetPrototypeMethod(ctor, "release", Release); Nan::SetPrototypeMethod(ctor, "subtract", Subtract); + Nan::SetPrototypeMethod(ctor, "mul", Mul); target->Set(Nan::New("Matrix").ToLocalChecked(), ctor->GetFunction()); }; @@ -607,7 +608,7 @@ NAN_METHOD(Matrix::PixelCol) { int height = self->mat.size().height; int x = info[0]->IntegerValue(); v8::Local < v8::Array > arr; - + if (self->mat.channels() == 3) { arr = Nan::New(height * 3); for (int y = 0; y < height; y++) { @@ -1936,6 +1937,8 @@ NAN_METHOD(Matrix::WarpAffine) { int dstCols = info[2]->IsUndefined() ? self->mat.cols : info[2]->Uint32Value(); cv::Size resSize = cv::Size(dstRows, dstCols); + printf("dstRows %i", dstRows); + cv::warpAffine(self->mat, res, rotMatrix->mat, resSize); ~self->mat; self->mat = res; @@ -2835,3 +2838,22 @@ NAN_METHOD(Matrix::Subtract) { return; } + +NAN_METHOD(Matrix::Mul) { + SETUP_FUNCTION(Matrix) + + if (info.Length() < 1) { + Nan::ThrowTypeError("Invalid number of arguments"); + } + + Matrix *other = Nan::ObjectWrap::Unwrap(info[0]->ToObject()); + + cv::Mat res = self->mat.mul(other->mat); + + Local out = Nan::NewInstance(Nan::GetFunction(Nan::New(Matrix::constructor)).ToLocalChecked()).ToLocalChecked(); + Matrix *m_out = Nan::ObjectWrap::Unwrap(out); + m_out->mat = res; + + info.GetReturnValue().Set(out); + return; +} diff --git a/src/Matrix.h b/src/Matrix.h index c9a9875..4c359c2 100755 --- a/src/Matrix.h +++ b/src/Matrix.h @@ -134,6 +134,7 @@ public: JSFUNC(Release) JSFUNC(Subtract) + JSFUNC(Mul) /* static Handle Val(const Arguments& info); static Handle RowRange(const Arguments& info); From 0cc8a3e86ebc855b0849e6aa63a3ffd6432593e0 Mon Sep 17 00:00:00 2001 From: Pierre Colle Date: Thu, 31 Aug 2017 16:10:18 +0200 Subject: [PATCH 11/12] error message --- lib/opencv.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/opencv.js b/lib/opencv.js index 283dc8a..8007e61 100755 --- a/lib/opencv.js +++ b/lib/opencv.js @@ -74,7 +74,7 @@ var getBufferMethodName = function(bytes, dataType, endianness, read){ } else if(bytes === 16){ fnName += (dataType === "U" ? "U" : "")+"Int16"+endianness; } else { - throw("This matrix type ("+type+") is not compatible with fromArray, you can implement if you need it") + throw("This matrix type ( CV_"+bytes+dataType+") is not compatible with fromArray, you can implement if you need it") } return fnName; }; From 0722569d7e99c7742cc9292bb311c10dd8076e12 Mon Sep 17 00:00:00 2001 From: Pierre Colle Date: Tue, 5 Sep 2017 16:49:28 +0200 Subject: [PATCH 12/12] clean logs and error messages --- lib/opencv.js | 2 +- src/Matrix.cc | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/opencv.js b/lib/opencv.js index 8007e61..6184fbe 100755 --- a/lib/opencv.js +++ b/lib/opencv.js @@ -74,7 +74,7 @@ var getBufferMethodName = function(bytes, dataType, endianness, read){ } else if(bytes === 16){ fnName += (dataType === "U" ? "U" : "")+"Int16"+endianness; } else { - throw("This matrix type ( CV_"+bytes+dataType+") is not compatible with fromArray, you can implement if you need it") + throw("This matrix type (CV_"+bytes+dataType+") is not compatible with fromArray/toArray") } return fnName; }; diff --git a/src/Matrix.cc b/src/Matrix.cc index 586f43d..8d6e9a7 100755 --- a/src/Matrix.cc +++ b/src/Matrix.cc @@ -1937,8 +1937,6 @@ NAN_METHOD(Matrix::WarpAffine) { int dstCols = info[2]->IsUndefined() ? self->mat.cols : info[2]->Uint32Value(); cv::Size resSize = cv::Size(dstRows, dstCols); - printf("dstRows %i", dstRows); - cv::warpAffine(self->mat, res, rotMatrix->mat, resSize); ~self->mat; self->mat = res;