merge compare with piercus's master branch

This commit is contained in:
Pierre Colle 2017-09-05 17:47:10 +02:00
commit 6de971d0e8
12 changed files with 568 additions and 15 deletions

View File

@ -20,7 +20,8 @@
"src/Calib3D.cc", "src/Calib3D.cc",
"src/ImgProc.cc", "src/ImgProc.cc",
"src/Stereo.cc", "src/Stereo.cc",
"src/LDAWrap.cc" "src/LDAWrap.cc",
"src/Histogram.cc",
], ],
"libraries": [ "libraries": [

67
examples/calc-hist.js Normal file
View File

@ -0,0 +1,67 @@
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 = 256,
range = [0, 256],
uniform = true,
histFile = 'files/chart2.png';
/// 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);
//////
// 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)
});
*/
});

161
examples/emd.js Normal file
View File

@ -0,0 +1,161 @@
var cv = require('../lib/opencv');
//
// 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 (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');
///////////////////
// 1. Build 2 histograms from images using calcHist
//////////////////
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);
var secondImageHist64 = cv.histogram.calcHist(im2, channels, size, range, uniform);
//////////////
// 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 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 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");
//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 = luvMiddles[0].map(function(luvMiddle1){
return luvMiddles[0].map(function(luvMiddle2){
return [distance(luvMiddle1, luvMiddle2)];
})
});
//////
// 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)
});
});

View File

@ -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"));
}
}
}
}
});

View File

@ -1,7 +1,8 @@
var Stream = require('stream').Stream var Stream = require('stream').Stream
, Buffers = require('buffers') , Buffers = require('buffers')
, util = require('util') , util = require('util')
, path = require('path'); , path = require('path')
, os = require('os');
var cv = module.exports = require('./bindings'); var cv = module.exports = require('./bindings');
@ -33,6 +34,120 @@ Matrix.prototype.inspect = function(){
return "[ Matrix " + size + " ]"; 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 (CV_"+bytes+dataType+") is not compatible with fromArray/toArray")
}
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<rows * cols * channels;i++){
var c = i%channels;
var r = Math.floor(i/channels);
var y = r%cols;
var x = Math.floor(r/cols);
buf[fnName](arr[x][y][c], i*n_bytes);
}
mat.put(buf);
return mat;
}
Matrix.prototype.toArray = function(){
var size = this.size();
var buf = this.getData();
var type = this.type();
var bytesAndChannels = getNumberOfBytesAndChannelsPerType(type);
var bytes = bytesAndChannels.bytes;
var channels = bytesAndChannels.channels || this.channels();
var dataType = bytesAndChannels.dataType;
var n_bytes = bytes/8;
var fnName = getBufferMethodName(bytes, dataType, os.endianness(), true)
var res = []
for(var i = 0; i < size[0]; i++){
var row = [];
for(var j = 0; j < size[1]; j++){
var channelsValues = [];
for(var k = 0; k < channels; k++){
var index = (i*size[1]+j)*channels+k;
channelsValues.push(buf[fnName](index*n_bytes));
}
row.push(channelsValues);
}
res.push(row);
}
return res;
}
ImageStream = cv.ImageStream = function(){ ImageStream = cv.ImageStream = function(){
this.writable = true; this.writable = true;
@ -148,6 +263,3 @@ var CASCADES = {
Object.keys(CASCADES).forEach(function(k){ Object.keys(CASCADES).forEach(function(k){
cv[k] = path.resolve(__dirname, '../data', CASCADES[k]) cv[k] = path.resolve(__dirname, '../data', CASCADES[k])
}) })

View File

@ -96,6 +96,7 @@ void Constants::Init(Local<Object> target) {
CONST_INT(CV_DIST_C); CONST_INT(CV_DIST_C);
CONST_INT(CV_DIST_L1); CONST_INT(CV_DIST_L1);
CONST_INT(CV_DIST_L2); CONST_INT(CV_DIST_L2);
CONST_INT(CV_DIST_USER);
CONST_INT(CV_DIST_MASK_3); CONST_INT(CV_DIST_MASK_3);
CONST_INT(CV_DIST_MASK_5); CONST_INT(CV_DIST_MASK_5);

135
src/Histogram.cc Normal file
View File

@ -0,0 +1,135 @@
#include "Histogram.h"
#include "Matrix.h"
void Histogram::Init(Local<Object> target) {
Nan::Persistent<Object> inner;
Local<Object> obj = Nan::New<Object>();
inner.Reset(obj);
Nan::SetMethod(obj, "calcHist", CalcHist);
Nan::SetMethod(obj, "emd", Emd);
target->Set(Nan::New("histogram").ToLocalChecked(), obj);
}
NAN_METHOD(Histogram::CalcHist) {
Nan::EscapableHandleScope scope;
try {
// Arg 0 is the image
Matrix* m0 = Nan::ObjectWrap::Unwrap<Matrix>(info[0]->ToObject());
cv::Mat inputImage = m0->mat;
// Arg 1 is the channel
Local<Array> nodeChannels = Local<Array>::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();
}
// Arg 2 is histogram sizes in each dimension
Local<Array> nodeHistSizes = Local<Array>::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<Array> nodeRanges = Local<Array>::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<Array> nodeRange = Local<Array>::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;
ranges[i] = histRanges[i];
}
// Arg 4 is uniform flag
bool uniform = info[4]->BooleanValue();
// Make a mat to hold the result image
cv::Mat outputHist;
// Perform calcHist
cv::calcHist(&inputImage, 1, channels, cv::Mat(), outputHist, dims, histSize, ranges, uniform);
v8::Local<v8::Array> arr = Nan::New<Array>(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<Number>(outputHist.at<float>(i)));
} else {
v8::Local<v8::Array> arr2 = Nan::New<Array>(dims);
for (unsigned int j=0; j < (unsigned int) histSize[1]; j++) {
if(dims <= 2){
arr2->Set(j, Nan::New<Number>(outputHist.at<float>(i,j)));
} else {
v8::Local<v8::Array> arr3 = Nan::New<Array>(dims);
for (unsigned int k=0; k < (unsigned int) histSize[1]; k++) {
arr3->Set(k, Nan::New<Number>(outputHist.at<float>(i,j,k)));
}
arr2->Set(j, arr3);
}
}
arr->Set(i, arr2);
}
}
info.GetReturnValue().Set(arr);
} catch (cv::Exception &e) {
const char *err_msg = e.what();
Nan::ThrowError(err_msg);
return;
}
}
// cv::distanceTransform
NAN_METHOD(Histogram::Emd) {
Nan::EscapableHandleScope scope;
try {
// Arg 0 is the first signature
//std::vector<std::vector<float>> sig1 = nodeArrayToVec(info[0]->ToObject());
Matrix* m0 = Nan::ObjectWrap::Unwrap<Matrix>(info[0]->ToObject());
cv::Mat sig1 = m0->mat;
// Arg 1 is the second signature
//std::vector<std::vector<float>> sig2 = nodeArrayToVec(info[1]->ToObject());
Matrix* m1 = Nan::ObjectWrap::Unwrap<Matrix>(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<Matrix>(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;
}
}

16
src/Histogram.h Normal file
View File

@ -0,0 +1,16 @@
#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<Object> target);
static NAN_METHOD(CalcHist);
static NAN_METHOD(Emd);
};
#endif

View File

@ -117,6 +117,7 @@ void Matrix::Init(Local<Object> target) {
Nan::SetPrototypeMethod(ctor, "release", Release); Nan::SetPrototypeMethod(ctor, "release", Release);
Nan::SetPrototypeMethod(ctor, "subtract", Subtract); Nan::SetPrototypeMethod(ctor, "subtract", Subtract);
Nan::SetPrototypeMethod(ctor, "compare", Compare); Nan::SetPrototypeMethod(ctor, "compare", Compare);
Nan::SetPrototypeMethod(ctor, "mul", Mul);
target->Set(Nan::New("Matrix").ToLocalChecked(), ctor->GetFunction()); target->Set(Nan::New("Matrix").ToLocalChecked(), ctor->GetFunction());
}; };
@ -2853,7 +2854,23 @@ NAN_METHOD(Matrix::Compare) {
cv::Mat res = cv::Mat(width, height, CV_8UC1); cv::Mat res = cv::Mat(width, height, CV_8UC1);
cv::compare(self->mat, other->mat, res, cmpop); cv::compare(self->mat, other->mat, res, cmpop);
Local<Object> out = Nan::NewInstance(Nan::GetFunction(Nan::New(Matrix::constructor)).ToLocalChecked()).ToLocalChecked();
Matrix *m_out = Nan::ObjectWrap::Unwrap<Matrix>(out);
m_out->mat = res;
info.GetReturnValue().Set(out);
return;
}
NAN_METHOD(Matrix::Mul) {
SETUP_FUNCTION(Matrix)
if (info.Length() < 1) {
Nan::ThrowTypeError("Invalid number of arguments");
}
Matrix *other = Nan::ObjectWrap::Unwrap<Matrix>(info[0]->ToObject());
cv::Mat res = self->mat.mul(other->mat);
Local<Object> out = Nan::NewInstance(Nan::GetFunction(Nan::New(Matrix::constructor)).ToLocalChecked()).ToLocalChecked(); Local<Object> out = Nan::NewInstance(Nan::GetFunction(Nan::New(Matrix::constructor)).ToLocalChecked()).ToLocalChecked();
Matrix *m_out = Nan::ObjectWrap::Unwrap<Matrix>(out); Matrix *m_out = Nan::ObjectWrap::Unwrap<Matrix>(out);
m_out->mat = res; m_out->mat = res;

View File

@ -135,6 +135,7 @@ public:
JSFUNC(Subtract) JSFUNC(Subtract)
JSFUNC(Compare) JSFUNC(Compare)
JSFUNC(Mul)
/* /*
static Handle<Value> Val(const Arguments& info); static Handle<Value> Val(const Arguments& info);
static Handle<Value> RowRange(const Arguments& info); static Handle<Value> RowRange(const Arguments& info);

View File

@ -15,6 +15,7 @@
#include "Stereo.h" #include "Stereo.h"
#include "BackgroundSubtractor.h" #include "BackgroundSubtractor.h"
#include "LDAWrap.h" #include "LDAWrap.h"
#include "Histogram.h"
extern "C" void init(Local<Object> target) { extern "C" void init(Local<Object> target) {
Nan::HandleScope scope; Nan::HandleScope scope;
@ -30,6 +31,7 @@ extern "C" void init(Local<Object> target) {
Constants::Init(target); Constants::Init(target);
Calib3D::Init(target); Calib3D::Init(target);
ImgProc::Init(target); ImgProc::Init(target);
Histogram::Init(target);
#if CV_MAJOR_VERSION < 3 #if CV_MAJOR_VERSION < 3
StereoBM::Init(target); StereoBM::Init(target);
StereoSGBM::Init(target); StereoSGBM::Init(target);

View File

@ -398,15 +398,16 @@ test('Mean', function(assert) {
}); });
test('Compare', function(assert) { test('Compare', function(assert) {
var b = new cv.Matrix.Zeros(2, 2, cv.Constants.CV_8UC1); var a = cv.Matrix.fromArray([[[0],[-20]],[[2],[-18]]], cv.Constants.CV_8SC1);
var a = new cv.Matrix.Zeros(2, 2, cv.Constants.CV_8UC1); var b = cv.Matrix.fromArray([[[3],[-20]],[[0],[-16]]], cv.Constants.CV_8SC1);
a.set(0, 0, 3); var compare1 = a.compare(b, cv.Constants.CMP_EQ);
var compare2 = a.compare(b, cv.Constants.CMP_GT);
var compare3 = a.compare(b, cv.Constants.CMP_LE);
var compare = a.compare(b, cv.Constants.CMP_EQ); assert.deepEqual(compare1.toArray(), [[[0],[255]],[[0],[0]]]);
var buf = compare.getData(); assert.deepEqual(compare2.toArray(), [[[0],[0]],[[255],[0]]]);
compare.save("./test.png"); assert.deepEqual(compare3.toArray(), [[[255],[255]],[[0],[255]]]);
console.log(compare.norm());
assert.end(); assert.end();
}); });
@ -463,5 +464,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. // Test the examples folder.
require('./examples')() require('./examples')()