引子:
opencv学习梯度不算很平坦,其中一个很重要的原因就是cv复杂度,cv中函数其特定的使用场景,对不同函数使用的参数不同,结果会差距很大。cv虽然提供了快速gui接口,也可以用cvui直接渲染控件到界面上,简单的任务基本应付。但对一些复杂算法,除了需要大量的方法、参数微调之外,还需要有记录和分析对比等一系列数据报告。前一段时间在学习Blender的节点系统,参数调节所见即所得非常直观,工作效率大为提升。试用一段时间后感觉可以在opencv中搞点动作,用最直观的方法来开发opencv功能。
但在节点系统选型上,纠结了很久,imgui-node-editor是第一选择,因为它依赖少,跨平台性很好,渲染上显示上没有太大的缺陷,但是没有节点计算逻辑涉及,就是说自己去实现节点计算逻辑,缺乏较好的应用案例,这也是我最终放弃imgui-node-editor的一个主要原因。
Litegraph.js 是基于javascript的,互交以canvas为基础的,原生节点拿来可就用。以此为基础,可以实现大部分界面互交。虽然关于litegraph.js开发的资料比较少,但是litegraph.js本体就是一个很好的学习案例,可以参考如shader节点、MIDI处理等模块,webglstudio.js-master也非常值得参考。另外litegraph.js也是支持节点计算和节点拓展功能。自定义节点写起来比较清晰,我后面会花一点时间来介绍。
js和python接口:EEL是一个可选项,在python程序中标注下接口,即可以通过js来访问该接口以此来执行python函数,同样道理,也可以在Web中标注函数接口,让python来调用该接口进行页面更新。EEL可以作为一个服务器后台程序运行也可以一个Chrome独立程序直接运行。
图像接口:基础对象如数值、字符串等都可以方便的传输,图像稍微麻烦点,传输需要用到base64格式作为中间格式往来于各个节点之间,python接口在收到base64格式的参数后,转换成为np的array格式后即可进行后续处理,计算结束后再以base64格式返回js。
举个简单例子:
常规方法
比如需要对图像做一个消除孤立点的工作,所需的python代码如下,代码需要对图像做几步操作,打开原始图像,一次dilate,一次Canny,一次connectedComponentsWithStats,最后根据阈值进行孤立点涂色或标注等等,最后输出图像,代码如下
import cv2
import numpy as np
filename="image/test.jpg"
img = cv2.imread(filename,0)
kernel = np.ones((3,3),np.uint8)
dilate = cv2.dilate(img,kernel,iterations = 1)
canny1=cv2.Canny(dilate,100,200)
_, labels, stats, centroids = cv2.connectedComponentsWithStats(canny1)
for istat in stats:
if istat[4]<120:
if istat[3]>istat[4]:
r=istat[3]
else:r=istat[4]
cv2.rectangle(canny1,tuple(istat[0:2]),tuple(istat[0:2]+istat[2:4]) , 0,thickness=-1)
cv2.imwrite('canny1.jpg',canny1)
我们的方法
通过节点实现的,开发的界面如下
一个典型节点的例子,旋转图片,有两个控制
第一个参数控制图像旋转的角度,用拖动控制角度的大小
第二个参数控制图像是否需要保留裁剪(图像原始大小会改变)
节点功能划分不一定非常科学,所以按颜色、输入输出、变换、算子来划分。已经实现的Opencv节点如下,
颜色
名称 | 简介 |
---|---|
rgb | rgb三色 |
hsv | hsv三色 |
hsv2rgb | 颜色系统转换 |
rgb2hsv | 颜色系统转换 |
输入
名称 | 简介 |
---|---|
image | 文件图像输入 |
camera | 摄像机输入 |
weburl | Web文件视频输入 |
输出
名称 | 简介 |
---|---|
Preview | 图像显示 |
Save image | 图像保存 |
图像变换
名称 | 简介 |
---|---|
rotate | 旋转 |
scale | 拉伸 |
shape | 获取大小 |
Split | 分色 |
Merge | 合色 |
connectedComponents | 联通区域 |
图像算子
名称 | 简介 |
---|---|
Threshold | 旋转 |
Canny | 拉伸 |
Gauss | 高斯模糊 |
Erode | 腐蚀 |
Dilate | 和膨胀 |
Filter | 筛选 |
标注
名称 | 简介 |
---|---|
Hist | 直方图 |
Drawstr | 标注文本(不支持中文) |
Rect | 标注矩形 |
Line | 标注线形 |
集合操作
名称 | 简介 |
---|---|
集合 | |
each | 对每个对象操作 |
Litegraph节点接口实现
为了实现对opencv图像功能的封装,除了需要进行节点初始化外,还自定义节点包括如下内容:
输入/输出节点、属性、节点部件(用于具体参数的控制或其他)
添加输入节点,第一个参数是节点名,第二个参数是节点类型,类型不同不允许连接
输入输出
//初始化阶段
this.addInput("A","number");
//添加输入节点,调用方式同输入节点
this.addOutput("A+B","number");
//其次需要实现liteGraph Node几个重要接口,最后要注册自己的节点
节点执行
//OnExecute阶段
//获得第一个输入节点的内容
this.getInputData(0);
//输出值到第一个输出节点
this.setOutputData(0,value);
//获得数据
A = getInputData(0)
//做点什么
var B = do_something(A)
//返回数据
setOutputData(0,B)
节点属性
属性很简单,是可以被序列化的内部变量,结构也不难
this.properties = {
kernel_size: 5,
iterate_times: 1,
kernel_type:"MORPH_RECT",
};
执行
Litegraph.js 会根据连接结构,自动计算节点优先级,决定哪些节点先计算,哪些节点后计算。这里只需要把每个节点的”任务”安排好,坐享其成即可,看上去是不是很简单。
OnExecute接口在graph.runStep() 或graph.run() 执行时会被调用,runStep()是进行指定有限数量,run则是不断进行计算直到接受到stop()信号,在计算负荷较重的场景,建议使用runStep()替代run()
节点高级内容
节点部件
包括基本的组件:
slider 、text、button都是一致的
text输入的时候会出现一个输入框,在触屏的环境没有试验过,这个困扰了我很久很久,到最后才发现是css引用出了问题,eel调试js还是要用chrome的日志来解决,这和js调试是一致的,
slider 滑轨
combo 类似于下拉框
toggle 类似于checkbox
缺点:
没有color picker ,slider仅支持浮点,但都不是大问题,对于自定义节点绘图,使用此接口进行二次开发,当节点塌陷的情况不需要执行,静态为主的节点可以绘制到image对象中减少2d重绘,背景绘制可以参考如下代码
onDrawBackground()= function(){
if(this.flags.collapsed)
return;
ctx.save();
ctx.fillColor = "black";
ctx.fillRect(0,0,10,this.size[1]);
ctx.restore();
}
类似的js问题还有不少,
比如that和this的问题,js老鸟都懂的
还有比如 EEL返回值的问题
之前是这样想的,结果一直取不到,折腾了将近一个礼拜
var B = eel.color_filter(A,low,upper);
this.setOutputData(0,B);
后来只有阅读eel的源码,知道eel返回的是一个promise对象,因为以前做js异步编程的机会比较少,没真正学透造成的
var B = eel.color_filter(A,low,upper);
B().then(
(data)=>{
this.setOutputData(0,data);
},
(err)=> {this.setOutputData(0,"data:image/gif;base64,R0lGODlhAQABAAAAACwAAAAAAQABAAA=");}
)
这三个接口仅在需要处理用户互交的场景使用,比如绘图、标记等,可以参考源码CurveEditor部分来学习。
onMouseDown()
onMouseMove()
onMouseUp()
效果展示
gitee似乎没法上传了,github代码地址如下
https://github.com/touchmaker/opencv-node-edit
eel_run.py <-- Python scripts
switch.py
web/ <-- Web folder
test01.jpg
test02.jpg
test03.jpg
main.html
css/
menu.css
js/
litegraph.core.js
comm.js
curvenode.js
nodecv.js
menu.js
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a0v5K2uH-1664352261858)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/28deadf63c214db7bca1b7a96b1a83a7~tplv-k3u1fbpfcp-zoom-1.image)]
文章写到这里,觉得屏幕前的自己比较磨叽
项目从开始到现在陆陆续续做了2个月左右,自己写的代码绝没有超过2000行,能用如此精简的代码实现该需求,自己也感到不可思议,
我只做代码的搬运工,自然也没有必要孤芳自赏,稍微修改后转投github,希望有兴趣的同志一起加入,维护和issue问题,提出想法
很多同学现在的也在磨刀霍霍准备跳槽,不管是做前端的还是后台猴子,能坚守在一线的程序员越来越少了