前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >教你机器视觉如何实现一个工业相机二次开发框架

教你机器视觉如何实现一个工业相机二次开发框架

作者头像
周旋
发布2024-01-17 14:18:35
2440
发布2024-01-17 14:18:35
举报
文章被收录于专栏:行走的机械人行走的机械人

工业相机二次开发是机器视觉行业必不可少的技能之一。

而如何实现一个框架,能够兼容所有工业相机二次开发,从而支持多种类型的工业相机,就是机器视觉行业的进阶技能了。

重明工业相机二次开发项目就是在实现相机二开框架的基础上,完成了海康工业相机的二次开发。项目源码下载地址:

https://www.roundvision.cc/softwaredevelopment/qt/chongming/

技术栈:

重明工业相机二次开发项目框架如下图所示:

整个项目分前端部分的界面设计,和后端部分的工业相机框架设计。

1、界面GUI实现

重明的界面实现非常简洁,主要为三个部分:

左侧的相机列表,中间的图像显示,右侧的相机参数属性列表。

控制窗口的实现非常简单,其实就是一排按钮加一个QListWidget列表,用来显示所有检测到的工业相机。

视觉窗口用来显示图像,采用QT的视图模型框架,采用QGrapicsScene来实现的。

属性窗口主要涉及到了QT的MVD框架,即Model-View-Delegate框架,模型-视图-代理,通过视图代理,完成了对各个不同属性参数类型的支持,完成了相机参数属性Int,double,bool,cmd,string等多种类型的显示。

2、后端框架接口

实现了前端界面,现在我们可以考虑,如何抽象工业相机接口类,实现对不同工业相机的无差别接入,达到工业相机二次开发框架的效果呢?

这里可以借用QT插件的便利性,来设计工业相机抽象插件接口:

代码语言:javascript
复制
 //相机接口类
class CameraInterface
{
public:
  CameraInterface(const CameraMetaInfo& info)
  {
    m_cameraInfo = info;
  }
  virtual ~CameraInterface() {}
  //获取相机用户定义名称
  virtual std::string UserName()
{
    return m_cameraInfo.UserDefineID;
  }
  //获取相机序列号
  virtual std::string Serial()
{
    return m_cameraInfo.Serial;
  }
  //获取相机参数列表
  virtual uint32_t getParamList(std::vector<CameraParam>& paramList) = 0;
  //判断相机是否连接
  virtual bool isConnect() = 0;
  //判断相机是否拉流
  virtual bool isGrabbing() = 0;
  //初始化相机对象
  virtual uint32_t acquire() = 0;
  //释放相机
  virtual uint32_t release() = 0;
  //连接相机
  virtual uint32_t connect() = 0;
  //断开连接
  virtual uint32_t disconnect() = 0;
  //创建拉流资源
  virtual uint32_t creatStream() = 0;
  //销毁拉流资源
  virtual uint32_t destroyStream() = 0;
  //开启拉流
  virtual uint32_t startGrabbing() = 0;
  //停止拉流
  virtual uint32_t stopGrabbing() = 0;
  //导入配置文件
  virtual uint32_t loadConfig(const std::string path) = 0;
  //导出配置文件
  virtual uint32_t saveConfig(const std::string path) = 0;
  //获取配置文件格式
  virtual std::string configFormat() = 0;
  //读取相机参数
  virtual uint32_t readParam(CameraParam& param) = 0;
  //写入相机参数
  virtual uint32_t writeParam(CameraParam& param) = 0;
  //获取实时图像
  virtual uint32_t getImageLast(cv::Mat& image) = 0;
  //获取图像队列
  virtual CameraImageQueue& ImageQueue()
{
    return m_imageQueue;
  }

protected:
  CameraImageQueue m_imageQueue;//图像队列
  std::vector<CameraParam> m_cameraParams;//相机参数列表
  CameraMetaInfo m_cameraInfo;//相机元信息
};

通过抽象设计统一的相机行为接口,在通过层层封装,即可达到框架效果。

3、如何实现相机图像队列

相机出图速度是有差异的,而我们处理相机出图也会有所耗时,如果你是出一张图像处理一张,然后再去拿一张图像,那很容易造成丢帧的问题。所以设计一个缓冲队列是非常有必要的。

我们的图像队列内部会包含两个队列,一个空闲队列,一个工作队列。

在我们相机图像队列这个应用场景下,生产者就是相机SDK的回调函数,该回调函数会生成相机的原始图像数据,我们在回调函数内将原始图像数据加入到队列中。加入到队列是先看空闲队列有没有位置,如果有则加入到空闲队列,然后触发信号量激活消费者。如果空闲队列没有位置,则从工作队列取出最旧的图像,将原始数据加入到该位置。

我们的消费者,就是我们的取图线程,我们软件会不停的从队列中的工作队列中尝试取出图像,当工作队列为空时,会阻塞在信号量中,当生产者生产了一张图像后,会激活该信号量使取图线程取到图像。

图像队列代码实现:

代码语言:javascript
复制
#define TIME_OUT_MS    5000  //取图超时时间
#define ImageQueueSize  10    //图像队列长度宏定义

class CameraImageQueue
{
public:
  CameraImageQueue();
  CameraImageQueue(int maxSize);
  //向图像队列中加入图像
  uint32_t Put(const cv::Mat& m);
  //从图像队列中取出图像
  uint32_t Take(cv::Mat& m);
  //队列是否为空
  bool Empty();
  //队列是否为满
  bool Full();
  //队列当前长度
  size_t Size();
private:
  bool isFull() const
{
    bool full = workImageQueue.size() >= m_queueSize;
    return full;
  }

  bool isEmpty() const
{
    bool empty = workImageQueue.empty();
    return empty;
  }

  bool NotFull() const
{
    bool full = workImageQueue.size() >= m_queueSize;
    return !full;
  }

  bool NotEmpty() const
{
    bool empty = workImageQueue.empty();
    return !empty;
  }

private:
  std::mutex m_mutex;
  std::condition_variable m_condition;
  std::queue<cv::Mat> freeImageQueue;//空闲队列
  std::queue<cv::Mat> workImageQueue;//工作队列

  uint8_t m_queueSize;
  bool m_needStop;
};

总结

项目由丰富的视频教程,见BiliBili:

视频链接:

https://www.bilibili.com/video/BV1pp4y1n7X9

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2024-01-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 周旋机器视觉 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档