diff --git a/cc/imgproc/imgproc.cc b/cc/imgproc/imgproc.cc index fddf6b666..a2c55f7dd 100644 --- a/cc/imgproc/imgproc.cc +++ b/cc/imgproc/imgproc.cc @@ -6,29 +6,10 @@ #include "imgprocBindings.h" #include "imgprocConstants.h" -#define FF_DEFINE_CALC_HIST(name, n, constRangesArray) \ - cv::MatND name(cv::Mat img, cv::Mat mask, int channels[], int histSize[], std::vector rangesVec) { \ - const float* ranges[] = constRangesArray; \ - cv::MatND hist; \ - cv::calcHist(&img, 1, channels, mask, hist, n, histSize, ranges, true, false); \ - return hist; \ - } - -#define FF_HIST_RANGE_1 { rangesVec.at(0) } -#define FF_HIST_RANGE_2 { rangesVec.at(0), rangesVec.at(1) } -#define FF_HIST_RANGE_3 { rangesVec.at(0), rangesVec.at(1), rangesVec.at(2) } -#define FF_HIST_RANGE_4 { rangesVec.at(0), rangesVec.at(1), rangesVec.at(2), rangesVec.at(3) } - -FF_DEFINE_CALC_HIST(calcHist1, 1, FF_HIST_RANGE_1); -FF_DEFINE_CALC_HIST(calcHist2, 2, FF_HIST_RANGE_2); -FF_DEFINE_CALC_HIST(calcHist3, 3, FF_HIST_RANGE_3); -FF_DEFINE_CALC_HIST(calcHist4, 4, FF_HIST_RANGE_4); - NAN_MODULE_INIT(Imgproc::Init) { ImgprocConstants::Init(target); Nan::SetMethod(target, "getStructuringElement", GetStructuringElement); Nan::SetMethod(target, "getRotationMatrix2D", GetRotationMatrix2D); - Nan::SetMethod(target, "calcHist", CalcHist); Nan::SetMethod(target, "plot1DHist", Plot1DHist); Nan::SetMethod(target, "fitLine", FitLine); Nan::SetMethod(target, "getAffineTransform", GetAffineTransform); @@ -61,7 +42,8 @@ NAN_MODULE_INIT(Imgproc::Init) { Nan::SetMethod(target, "accumulateSquareAsync", AccumulateSquareAsync); Nan::SetMethod(target, "accumulateWeighted", AccumulateWeighted); Nan::SetMethod(target, "accumulateWeightedAsync", AccumulateWeightedAsync); - + Nan::SetMethod(target, "calcHist", CalcHist); + Nan::SetMethod(target, "calcHistAsync", CalcHistAsync); Moments::Init(target); Contour::Init(target); @@ -128,85 +110,6 @@ NAN_METHOD(Imgproc::GetPerspectiveTransform) { info.GetReturnValue().Set(Mat::Converter::wrap(cv::getPerspectiveTransform(srcPoints, dstPoints))); } -NAN_METHOD(Imgproc::CalcHist) { - FF::TryCatch tryCatch("Imgproc::CalcHist"); - cv::Mat img, mask = cv::noArray().getMat(); - std::vector> _ranges; - if ( - Mat::Converter::arg(0, &img, info) || - Mat::Converter::optArg(2, &mask, info) - ) { - return tryCatch.reThrow(); - } - if (!info[1]->IsArray()) { - return tryCatch.throwError("expected arg 1 to be an array"); - } - v8::Local jsHistAxes = v8::Local::Cast(info[1]); - - cv::Mat inputImg = img; - int inputType = CV_MAKETYPE(CV_32F, img.channels()); - if (inputType != img.type()) { - img.convertTo(inputImg, inputType); - } - - int dims = jsHistAxes->Length(); - int* channels = new int[dims]; - int* histSize = new int[dims]; - std::vector ranges; - // TODO replace old macros - for (int i = 0; i < dims; ++i) { - ranges.push_back(new float[dims]); - v8::Local jsAxis = Nan::To((Nan::Get(jsHistAxes, i).ToLocalChecked())).ToLocalChecked(); - if (!FF::hasOwnProperty(jsAxis, "ranges")) { - return tryCatch.throwError("expected axis object to have ranges property"); - } - v8::Local jsRangesVal = Nan::Get(jsAxis, Nan::New("ranges").ToLocalChecked()).ToLocalChecked(); - if (!jsRangesVal->IsArray()) { - return tryCatch.throwError("expected ranges to be an array"); - } - v8::Local jsRanges = v8::Local::Cast(jsRangesVal); - if (jsRanges->Length() != 2) { - return tryCatch.throwError("expected ranges to be an array of length 2"); - } - ranges.at(i)[0] = FF::DoubleConverter::unwrapUnchecked(Nan::Get(jsRanges, 0).ToLocalChecked()); - ranges.at(i)[1] = FF::DoubleConverter::unwrapUnchecked(Nan::Get(jsRanges, 1).ToLocalChecked()); - int channel, bins; - - if (FF::IntConverter::prop(&channel, "channel", jsAxis) || FF::IntConverter::prop(&bins, "bins", jsAxis)) { - return tryCatch.reThrow(); - } - channels[i] = channel; - histSize[i] = bins; - } - - cv::MatND hist; - if (dims == 1) { - hist = calcHist1(inputImg, mask, channels, histSize, ranges); - } - else if (dims == 2) { - hist = calcHist2(inputImg, mask, channels, histSize, ranges); - } - else if (dims == 3) { - hist = calcHist3(inputImg, mask, channels, histSize, ranges); - } - else if (dims == 4) { - hist = calcHist4(inputImg, mask, channels, histSize, ranges); - } - - for (int i = 0; i < dims; ++i) { - delete[] ranges.at(i); - } - delete[] channels; - delete[] histSize; - - int outputType = CV_MAKETYPE(CV_64F, img.channels()); - if (outputType != hist.type()) { - hist.convertTo(hist, outputType); - } - - info.GetReturnValue().Set(Mat::Converter::wrap(hist)); -} - NAN_METHOD(Imgproc::Plot1DHist) { FF::TryCatch tryCatch("Imgproc::Plot1DHist"); @@ -431,4 +334,12 @@ NAN_METHOD(Imgproc::AccumulateWeightedAsync) { FF::asyncBinding("Imgproc", "AccumulateWeighted", info); } +NAN_METHOD(Imgproc::CalcHist) { + FF::syncBinding("Imgproc", "CalcHist", info); +} + +NAN_METHOD(Imgproc::CalcHistAsync) { + FF::asyncBinding("Imgproc", "CalcHist", info); +} + #endif \ No newline at end of file diff --git a/cc/imgproc/imgproc.h b/cc/imgproc/imgproc.h index ba79c53a8..846c35fc0 100644 --- a/cc/imgproc/imgproc.h +++ b/cc/imgproc/imgproc.h @@ -18,7 +18,6 @@ class Imgproc { static NAN_METHOD(GetRotationMatrix2D); static NAN_METHOD(GetAffineTransform); static NAN_METHOD(GetPerspectiveTransform); - static NAN_METHOD(CalcHist); static NAN_METHOD(Plot1DHist); static NAN_METHOD(FitLine); static NAN_METHOD(GetTextSize); @@ -49,6 +48,8 @@ class Imgproc { static NAN_METHOD(AccumulateSquareAsync); static NAN_METHOD(AccumulateWeighted); static NAN_METHOD(AccumulateWeightedAsync); + static NAN_METHOD(CalcHist); + static NAN_METHOD(CalcHistAsync); }; #endif diff --git a/cc/imgproc/imgprocBindings.h b/cc/imgproc/imgprocBindings.h index 5d761c7f5..fc47d07ac 100644 --- a/cc/imgproc/imgprocBindings.h +++ b/cc/imgproc/imgprocBindings.h @@ -269,6 +269,127 @@ namespace ImgprocBindings { }; }; }; + + typedef struct HistAxes { + float range[2]; + int channel; + int bins; + } HistAxes; + + class HistAxesConverterImpl : public FF::UnwrapperBase { + public: + + typedef HistAxes Type; + + static std::string getTypeName() { + return std::string("HistAxes"); + } + + static bool assertType(v8::Local jsVal) { + if (!jsVal->IsObject()) return false; + + auto jsObj = Nan::To(jsVal).ToLocalChecked(); + + if (!FF::hasOwnProperty(jsObj, "ranges")) return false; + + auto jsRangesVal = Nan::Get(jsObj, Nan::New("ranges").ToLocalChecked()).ToLocalChecked(); + if (!jsRangesVal->IsArray()) return false; + auto jsRanges = v8::Local::Cast(jsRangesVal); + if (jsRanges->Length() != 2) return false; + + return ( + Nan::Get(jsObj, Nan::New("channel").ToLocalChecked()).ToLocalChecked()->IsNumber() || + Nan::Get(jsObj, Nan::New("bins").ToLocalChecked()).ToLocalChecked()->IsNumber() + ); + } + + static HistAxes unwrapUnchecked(v8::Local jsVal) { + FF::TryCatch tryCatch("Imgproc::CalcHist"); + HistAxes ret; + auto jsAxis = Nan::To(jsVal).ToLocalChecked(); + + v8::Local jsRangesVal = Nan::Get(jsAxis, Nan::New("ranges").ToLocalChecked()).ToLocalChecked(); + v8::Local jsRanges = v8::Local::Cast(jsRangesVal); + + ret.range[0] = FF::DoubleConverter::unwrapUnchecked(Nan::Get(jsRanges, 0).ToLocalChecked()); + ret.range[1] = FF::DoubleConverter::unwrapUnchecked(Nan::Get(jsRanges, 1).ToLocalChecked()); + FF::IntConverter::prop(&ret.channel, "channel", jsAxis); + FF::IntConverter::prop(&ret.bins, "bins", jsAxis); + return ret; + } + + static v8::Local wrap(HistAxes val) { + v8::Local ret = Nan::New(); + Nan::Set(ret, FF::newString("bins"), FF::IntConverter::wrap(val.bins)); + Nan::Set(ret, FF::newString("channel"), FF::IntConverter::wrap(val.channel)); + Nan::Set(ret, FF::newString("ranges"), FF::FloatArrayConverter::wrap({val.range[0], val.range[1]})); + + return ret; + } + }; + typedef FF::ArrayConverterTemplate ArrayHistAxesConverter; + + class CalcHist : public CvBinding { + private: + cv::MatND hist; + public: + v8::Local getReturnValue(){ + return Mat::Converter::wrap(hist); + } + + void setup() { + + auto src = req(); + auto jsHistAxes = req(); + auto mask = opt("mask", cv::noArray().getMat()); + + executeBinding = [=]() { + auto histAxes = jsHistAxes->ref(); + + const int dims = histAxes.size(); + + auto **ranges = new float*[dims]; + int *channels = new int[dims]; + int *bins = new int[dims]; + + for (int i = 0; i < dims; i++) { + auto entry = histAxes.at(i); + ranges[i] = new float[2]; + ranges[i][0] = entry.range[0]; + ranges[i][1] = entry.range[1]; + channels[i] = entry.channel; + bins[i] = entry.bins; + } + + auto img = src->ref(); + + cv::calcHist( + &img, + 1, + channels, + mask->ref(), + hist, + dims, + bins, + (const float **)(ranges), + true, + false + ); + + for (int i = 0; i < dims; ++i) { + delete[] ranges[i]; + } + delete[] ranges; + delete[] channels; + delete[] bins; + + int outputType = CV_MAKETYPE(CV_64F, img.channels()); + if (outputType != hist.type()) { + hist.convertTo(hist, outputType); + } + }; + } + }; } #endif \ No newline at end of file diff --git a/lib/typings/cv.d.ts b/lib/typings/cv.d.ts index a308eb0cd..8dd5fc927 100644 --- a/lib/typings/cv.d.ts +++ b/lib/typings/cv.d.ts @@ -38,6 +38,7 @@ export function blur(mat: Mat, kSize: Size, anchor?: Point2, borderType?: number export function blurAsync(mat: Mat, kSize: Size, anchor?: Point2, borderType?: number): Promise; export function NMSBoxes(bboxes: Rect[], scores: number[], scoreThreshold: number, nmsThreshold: number): number[]; export function calcHist(img: Mat, histAxes: HistAxes[], mask?: Mat): Mat; +export function calcHistAsync(img: Mat, histAxes: HistAxes[], mask?: Mat): Promise; export function calibrateCamera(objectPoints: Point3[], imagePoints: Point2[], imageSize: Size, cameraMatrix: Mat, distCoeffs: number[], flags?: number, criteria?: TermCriteria): { returnValue: number, rvecs: Vec3[], tvecs: Vec3[], distCoeffs: number[] }; export function calibrateCameraAsync(objectPoints: Point3[], imagePoints: Point2[], imageSize: Size, cameraMatrix: Mat, distCoeffs: number[], flags?: number, criteria?: TermCriteria): Promise<{ returnValue: number, rvecs: Vec3[], tvecs: Vec3[], distCoeffs: number[] }>; export function calibrateCameraExtended(objectPoints: Point3[], imagePoints: Point2[], imageSize: Size, cameraMatrix: Mat, distCoeffs: number[], flags?: number, criteria?: TermCriteria): { returnValue: number, rvecs: Vec3[], tvecs: Vec3[], distCoeffs: number[], stdDeviationsIntrinsics: Mat, stdDeviationsExtrinsics: Mat, perViewErrors: number[] }; diff --git a/test/tests/imgproc/imgprocTests.js b/test/tests/imgproc/imgprocTests.js index a8e03e28c..bf856baa7 100644 --- a/test/tests/imgproc/imgprocTests.js +++ b/test/tests/imgproc/imgprocTests.js @@ -145,6 +145,10 @@ module.exports = ({ cv, utils, getTestImg }) => { expect(() => cv.calcHist()).to.throw('Imgproc::CalcHist - Error: expected argument 0 to be of type'); }); + it('should throw if no HistAxes arg', () => { + expect(() => cv.calcHist(getTestImg())).to.throw('Imgproc::CalcHist - Error: expected argument 1 to be of type array of HistAxes'); + }); + it('should return 1 dimensional hist', () => { const histAxes = [ {