基於 Node-red實現opencv邊緣檢測節點
最近在學習如何將opencv的一些用例以節點的形式加入到Node-red供用戶使用,由於opencv邊緣檢測最簡單,所以用其練手,來熟悉下開發流程。node-red上opencv節點基本框架三部分模塊構成。
opencv邊緣檢測模塊
基於V8的js調用C++模塊
nodejs邊緣檢測模塊
下面是opencv邊緣檢測模塊用例,共有三個函數分別供node-red圖像輸入節點、圖像檢測節點和圖像輸出節點調用,另外使用libuv定時器,用於在各node節點初始化後模擬圖像輸入,該模塊最後將編譯成靜態庫的形式使用。
#include <iostream>
#include <stdlib.h>
#include <unistd.h>
#include <opencv2/opencv.hpp>
#include <string>
#include <uv.h>
#include "cv_edge.h"
using namespace std;
using namespace cv;
//global function and parameter called by timer
static sendMsgCb imageInMsgCb;
static Mat deliverImg;
//the timer to send the mat of image
uv_timer_t imageInTimer;
void timer_cb(uv_timer_t *handle)
{
printDebugMsg("C++ canny", "globalImg: %x", &deliverImg);
imageInMsgCb((unsigned long)&deliverImg);
}
int imageInInit(char *path, sendMsgCb cb){
int ret;
if(cb != NULL){
imageInMsgCb = cb;
}
if(path == NULL){
printDebugMsg("C++ canny", "the path is null");
return ERR_UNKNOWN;
}
printDebugMsg("C++ canny", "image in path: %s", path);
Mat img = imread(path, 0);
if(img.empty()){
printDebugMsg("C++ canny", "the path doesn't exist:%s", path);
return ERR_UNKNOWN;
}
deliverImg = img;
//intialize and start timer
ret = uv_timer_init(uv_default_loop(), &imageInTimer);
uv_timer_start(&imageInTimer, timer_cb, 1000, 0);
return ERR_NONE;
}
int imageEdgeCanny(unsigned long mat, sendMsgCb cb){
Mat result;
Canny(*(Mat *)mat, result, 50 ,150);
printDebugMsg("C++ canny", "mat before:%x, after:%x", mat, &result);
cb((unsigned long)&result);
return ERR_NONE;
}
int imageOutSave(unsigned long mat, char *path){
imwrite(path, *(Mat *)mat);
printDebugMsg("C++ canny","mat: %x, image out path: %s", mat, path);
return ERR_NONE;
}
有了C++程序,那js如何去調用C++程序?這裏就要用到google的v8引擎,v8引擎採用C++開發,利用V8我們將C++庫打包成nodejs模塊的形式供js程序調用,這樣我們就可以很方便的使用js調用C++接口,下面是基於v8圖像檢測程序,其中imageEdgeCannyV8會調用上面寫的opencv用例接口,最後,通過node-gyp工具就可以把opencv庫和v8模塊打包成後綴名爲.node的庫供js程序調用。
#include "cv_edge_addon.h"
#include <stdlib.h>
#include <string.h>
#include "_globalvar.h"
using namespace v8;
Handle<Value> imageEdgeCannyV8(const Arguments &args){
HandleScope scope;
//convert the mat js pointer to C++ pointer
V8_ASSERT(args[0]->IsUint32(), "args[0] parameters error!");
int arg0 = (int)args[0]->IntegerValue();
printDebugMsg("V8 image edge","arg0: %x", arg0);
//get the function for sending message, convert js function to C++ function pointer
V8_ASSERT(args[1]->IsFunction(), "arg[1] parameters error!");
cbArray[0] = Persistent<Function>::New(Local<Function>::Cast(args[1]));
sendMsgCb arg1;
arg1 = cbFunc0;
int ret = (int) imageEdgeCanny(arg0, arg1);
printDebugMsg("V8 image edge","return value: %d", ret);
//covert C++ int to js int
Handle<Value> retV8 = Int32::New(ret);
return scope.Close(retV8);
}
static void SetMemberFunc(Handle<Object> obj){
//register the interface for upper js to call
obj->Set(String::NewSymbol("imageEdgeCanny"),
FunctionTemplate::New(imageEdgeCannyV8)->GetFunction());
}
static void SetConst(Handle<Object> obj){
}
static void SetEnumConst(Handle<Object> obj){
}
static void SetGlobalVarFunc(Handle<Object> obj){
}
void Initcv_canny(Handle<Object> exports){
SetMemberFunc(exports);
SetConst(exports);
SetEnumConst(exports);
SetGlobalVarFunc(exports);
}
上述模塊編好之後,我們只需在js程序中使用require將模塊包含進來調用。在node-red中分別加入圖像輸入節點,圖像邊緣檢測節點和圖像輸出節點,下邊是圖像邊緣檢測節點的js程序
var IOLIB = require('../cv/addon');
var io = new IOLIB.IO({
log: true,
quickInit: false
});
var RedUtil = require('red-util');
module.exports = function(RED) {
function edgeDetect(config) {
console.log("edgeDetect intialize...");
RED.nodes.createNode(this,config);
var node = this;
var redUtil = new RedUtil(node, config);
var arg1 = function(){
console.log("send edgeDetect message");
node.send({
'payload':arguments[0]
});
}
node.on('input', function(msg){
//check if the msg is valid
if(!redUtil.isValid(msg))
return;
//covert json to int
var args = redUtil.msgToArgs(msg);
var arg0 = redUtil.jsonIntegerParse(args[0]);
io.imageEdgeCanny(arg0, arg1);
});
node.close('close', function(){
});
}
RED.nodes.registerType("edgeDetect", edgeDetect);
}
最後,打開node-red界面,將新加入的imageIn,edgeDetect,imageOut拖動工作區並連到一起,設置imageIn節點和imageOut的圖像讀取路徑和處理後的保存路徑。
點擊deploy,既可看到處理前和處理後的圖像。