前言
这个项目萌芽于2021年,期间在主职工作的空闲时间陆陆续续做了一些开发,包括软件框架平台和算法,这里做一下总结。
毕业后做了五年上位机软件,上一份工作离职时,感觉到对上位机的开发技能已经到了瓶颈,寻思着拓宽技能面,思考最终决定选视觉方向。去面试了两家视觉软件公司,结果没过,最终还是在原来的行业里面选择了一家公司。比较巧的是,入职的公司正在开展视觉软件二次开发任务尝试,由于之前工作有opencv的经验,便开始兼任视觉软件二次开发负责人。从视觉二次定制开发的积累,慢慢也发现他的局限性,最终,在参考商用成熟软件平台的基础上,也琢磨出来这款视觉拖拽软件平台。
做二次开发期间接触了多家商用算法库和平台软件,包括创科、凌云、OPT、海康、利珀。分析他们的平台软件,大致分为三层架构:
第一层,基础算子模块:纯算子
第二层,工具模块:调用算子,附带UI,并支持无限扩展
第三层,平台软件:工具模块的整合,支持拖拽、生产界面编辑等实际应用功能;
在支持底层算子二次开发的基础上,他们大多也支持软件平台层面的二次开发,提供工具参数修改以及图像嵌入等支持。
下面着重从几个开发瓶颈点来介绍自研视觉拖拽软件平台的开发历程。
一、可自由缩放的图像显示窗
在体验了商用视觉软件无比丝滑的图像查看窗后,面对opencv自带的图像显示窗体,图像压缩显示,无法像素级别查看等,完全无法忍受,后面就开始寻找自己写一个的方法。
一个偶然的机会,发现了GDI+的CImage类,完美解决问题。用它来绘制图像,支持像素级别查看,GDI+绘制图形、绘制文字的功能也能用上,也解决了图像处理结果准确显示的问题。
二、自定义图像类
图像处理中,图像类贯穿始终,opencv中是Mat,作为数据处理媒介,自研算法一定也要自己开发一个这样的处理类,用在自研算子的传参等场景。
分享两款商用算法的图像类基础定义,自研的图像类也和其类似:
//1
class scImage
{
public:
//图像宽度
long Width() const { return m_lWidth; }
//图像高度
long Height() const { return m_lHeight; }
/*
略
*/
//释放图像数据段,返回初始状态
virtual void Release() = 0;
protected:
unsigned char* m_pRawStorage; // 图像数据
long m_lWidth; // 图像宽度
long m_lHeight; // 图像高度
long m_lAlignModulus; // 图像对齐模式
long m_lWidthPadded; // 图像实际宽度(由于对齐的需要,可能对图像行数据进行扩展,因此,图像实际宽度 >= 图像宽度)
long m_lPixelSize; // 图像单像素占用的内存字节数(Gray:8位1字节,RGB:24位3字节)
};
//2
/* 图像基本功能类 */
class CPrImage
{
public:
CPrImage();
// 构造+复制
CPrImage( const CPrImage& imgSrc );
// 构造+创建
CPrImage( int nWidth, int nHeight, int nFormat=1 );
// 析构函数
virtual ~CPrImage();
/*
略
*/
// 释放
void Release();
protected:
int m_nWidth; // 宽度
int m_nHeight; // 高度
int m_nFormat; // 像素格式
int m_nLineByte; // 行字节数
UINT m_nSizeByte; // 总字节数
BYTE* m_pDataBits; // 图像数据
}; // CPrImage
三、自研算子
这一快没多少发言权,底子比较薄,也主要靠一腔热血摸索,磕磕绊绊写出来一些,主要有:
【图像匹配】
【相机标定】
【坐标系统】
【搜索直线】【搜索圆】等。
四、工具模块设计
工具模块是平台软件的基础,需要考虑各种兼容并包情况。
利用C++派生类与继承的面向对象程序设计思想,将基类做强大一点;新增工具直接派生自基类,只需要实现自身专用的功能代码,可以很好的解决工具无限扩展的问题。
基类如何设计,如何做大做强,这个在商用视觉平台中参考较少,其一般不提供自定义工具扩展。个人摸索出来基类设计一般要包含以下模块功能:
- 参数接口:提供属性参数、输入参数、输出参数等快捷增加的接口,派生类可快速配置;
- 显示功能:拖拽效果自绘制相关内容,派生的工具不再需要管理;
- 链接:支持参数变量的相互链接;
- 基础调参界面支持:大部分的工具不需要写界面,采用基类提供的标准界面调参即可。
工具实现类示例:
// 延时 工具快
class CtryControlDelay :public IToolControl
{
public:
CtryControlDelay();
~CtryControlDelay();
IToolBasis* Clone();
int Copy(const CtryControlDelay *pSrc);
int Exchange(trFileStore &fs);
public:
//执行
int Execute(IExecuteInOut *_p = NULL);
public:
int m_Time;
};
// 表达式 工具快
class CtryControlExpression :public IToolControl
{
public:
CtryControlExpression();
~CtryControlExpression();
IToolBasis* Clone();
int Copy(const CtryControlExpression *pSrc);
int Exchange(trFileStore &fs);
public:
//工具属性界面
bool HasPropertyWindow() { return true; }
int Property();
public:
//执行
int Execute(IExecuteInOut *_p = NULL);
public:
std::vector<tryData> m_Vars;
};
五、流程图设计与执行
这一模块是软件平台中最影响使用评分的一点,各家实现方式不一:
创科采用列表形式;
OPT采用列表+流程图形式;
凌云采用固定流程图;
海康采用自由分层的流程图;
个人最开始构思时,通过了PPT中的流程图进行模拟,因此选用的方案和凌云比较接近。
效果如下:
六、自定义生产界面
流程图设计对人员要求较高, 有一定上手难度。因此,催生出了更傻瓜式的生产界面,进行启、停、直接调参等操作。
商业软件里 凌云和海康这块做的比较完善,功能页丰富。
个人制作了一版,没充裕时间进行精细优化,以满足基本功能为主。
界面编辑效果:
一种软件平台独立使用时的生产界面效果:
七、平台软件的嵌入支持
自动化设备具备独立的控制软件,与视觉软件配合时形成两个软件来回切换,不便于操作,因此对视觉软件嵌入控制软件提出一些要求:
- 基本要求:视觉画面嵌入控制软件中显示——以凌云为例,就支持这种形式,其是通过OCX方式提供支持。
- 要求升级1:支持外部启停、提供变量访问操作接口——
- 要求升级2:支持方案切换——
- 要求升级3:画面支持外部绘图、提供鼠标消息响应——为了实现控制软件点动、显示加工细节等个性化操作;
对于升级3,各家开放程度不一,因为涉及主体架构,影响整体稳定性。以OPT为例,其2022年底才提供出相对完备的该类功能。
个人是通过带界面的C++动态库提供上述全部支持,支持多界面嵌入,流程编辑通过弹出窗体方式,控制软件嵌入效果附图:
结尾
敲文字还是比敲代码慢,中间断断续续多次才完成,算作一次总结。