目录
1、背景
在视觉项目开发过程中碰到了需要使用Halcon进行图像算法开发的需求,估计很多视觉工程师都用到过Halcon软件开发库,但是完成Halcon算法开发后就会遇到一个问题,就是图像的显示、读写、UI交互等问题,由于Halcon具有特殊的图像文件格式HObject和数据格式HTuple,所以说需要格式转换后才能实现相对应的操作,不过Halcon本身也有比较实用的显示、界面交互的功能,所以如何在C++或QT下使用这些功能成为了接下来需要去研究和实践的工作。
2、参考信息
Halcon针对不同的开发环境,给出了不同的开发例程,针对图形显示及界面操作这一块,Halcon只给出了C#的相关例程,其运行结果如下:
其中可以实现在窗口界面创建矩形、圆、椭圆等形状的Region,并根据鼠标来选择、拖动和设置尺寸,并实现设置颜色,获取坐标,region区内二值化、轮廓化等一系列后续操作。
3、目标
实现在QT环境下,将Halcon窗口贴在QT的控件上,并实现上述创建和操作region的基本动作。
4、步骤
4.1 Halcon库的配置
本人使用的是Halcon12.0的破解版,目前调用Halcon的函数不会出错,但是项目中有调用新版本的Halcon库有出错状况,目前未查证是不是版本的问题,Halcon配置主要在PRO文件中添加Include和Lib的引用路径。其中HALCONROOT是环境变量中Halcon的安装路径。
#includes
INCLUDEPATH += "$$(HALCONROOT)/include"
INCLUDEPATH += "$$(HALCONROOT)/include/halconcpp"
#libs
QMAKE_LIBDIR += "$$(HALCONROOT)/lib/$$(HALCONARCH)"
unix:LIBS += -lhalconcpp -lhalcon -lXext -lX11 -ldl -lpthread
win32:LIBS += "$$(HALCONROOT)/lib/$$(HALCONARCH)/halconcpp.lib" \
"$$(HALCONROOT)/lib/$$(HALCONARCH)/halcon.lib"
4.2 读取图像,并实现图像自适应窗体控件大小
这里我首先创建了一个QHalconWindow类,然后在qt的ui界面将widget提升为QHalconWindow类,这样就免去了Halcon窗口句柄和ui句柄的绑定,直接通过QHalconWindow类来调用就行。
qhalconwindow.h文件
#include <QObject>
#include <QWidget>
#include "HalconCpp.h"
class QHalconWindow : public QWidget
{
Q_OBJECT
public:
explicit QHalconWindow(QWidget *parent = 0,long Width=0,long Height=0);
virtual ~QHalconWindow(void);
HalconCpp::HTuple WindowID(void) {return WinID;} //f返回窗口句柄
protected:
void resizeEvent(QResizeEvent*); //窗口大小尺寸调整事件
private:
HalconCpp::HTuple WinID;
void OpenWindow(void);
}
Cpp文件主要是关于窗口基本操作的实现函数
#include "qhalconwindow.h"
using namespace HalconCpp;
QHalconWindow::QHalconWindow(QWidget *parent,long Width,long Height)
: QWidget(parent)
{
resize(Width,Height);
show();
OpenWindow();
}
QHalconWindow::~QHalconWindow(void)
{
CloseWindow(WindowID());
}
void QHalconWindow::OpenWindow(void)
{
SetWindowAttr("border_width",0);
SetCheck("~father");
HalconCpp::OpenWindow(0,0,100,100,(Hlong)winId(),"visible","",&WinID);
SetCheck("father");
}
//修改窗口尺寸
void QHalconWindow::resizeEvent(QResizeEvent *)
{
SetWindowExtents(WindowID(),0,0,width(),height());
}
参考Halcon中关于SetDrawingObjectCallback函数的描述,需要在c++下面调用时,调用C++格式的函数,即下图的Void的回调函数指针。
但是这个回调函数在程序中需要定义为一个全局函数,主要依据是Halcon中介绍,如下:
所以根据这些需求完成Halcon窗口中绘制矩形、圆形和直线的操作
4.3 主要的图形绘制和贴图操作见如下代码,其中重点为全局函数的创建来实现选择Select、拖拽Drag和尺寸Resize事件响应。
Widget.h文件
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QList>
#include <QStack>
#include <functional>
#include "HalconCpp.h"
//#include "qhalconwindow.h"
using namespace HalconCpp;
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
void InitWin(void);
static Widget* getInstance();
protected:
void resizeEvent (QResizeEvent*);
void InitFg(void);
private slots:
void on_HalconWinD_customContextMenuRequested(const QPoint &pos);
void onTaskBoxContextMenuEvent();
void onTaskDeleteObj();
void on_btn_DrawRectangle_clicked();
void on_btn_DrawCircle_clicked();
void on_btn_DrawLine_clicked();
void on_btn_ClearAllObj_clicked();
void AttachDrawObj(HDrawingObject obj);
void slot_ReceiveData(long);
signals:
void signal_data(long);
private:
Ui::Widget *ui;
//Halcon窗口的参数
HTuple WindowIDBuf,FGHandle,Width,Height,Area;
HTuple WindowWidth,WindowHeight;
HObject Image;
QStack<HObject> graphic_stack;
QList<HDrawingObject> drawing_objects;
HTuple Draw_Text;
QList<HTuple>Drawing_Index;
};
#endif // WIDGET_H
主要实现代码
#pragma execution_character_set("utf-8")
#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>
#include <QDebug>
#include <QMenu>
void CallBackFunc_Set(long DrawID,long WindowHandle, char* type);
void CallBackFunc_DrawObj(long DrawID,long WindowHandle, char* type);
HTuple selected_drawing_object;
Widget* instance;
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
WindowIDBuf = -1;
Draw_Text=HTuple();
InitWin();
instance = this;
connect(this,SIGNAL(signal_data(long)),this,SLOT(slot_ReceiveData(long)));
}
Widget::~Widget()
{
HalconCpp::CloseWindow(WindowIDBuf);
delete ui;
}
//建立自身调用
Widget *Widget::getInstance()
{
return instance;
}
void Widget::InitFg(void)
{
Hlong disp_width, disp_height;
//读取一张图像并获取图像大小
ReadImage(&Image,"D:/Test_Image/kobe.jpg");
GetImageSize(Image,&Width,&Height);
//根据图像的大小修改界面的尺寸大小
// disp_width = ui->HalconWinD->width();
// disp_height = ui->HalconWinD->height();
// ui->HalconWinD->resize(Width[0].L(),Height[0].L());
// resize(width()+Width[0].L()-disp_width,height()+Height[0].L()-disp_height);
}
void Widget::InitWin(void)
{
InitFg();
// HTuple hv_WindowHandleCurrent;
Hlong WinIDcurrent = (Hlong)ui->HalconWinD->winId();
WindowWidth = ui->HalconWinD->width();
WindowHeight = ui->HalconWinD->height();
OpenWindow(0,0,WindowWidth,WindowHeight,WinIDcurrent,"","",&WindowIDBuf);
AttachBackgroundToWindow(Image,WindowIDBuf);
// DispObj(Image,ui->HalconWinD->WindowID());
}
void Widget::resizeEvent(QResizeEvent *)
{
if(WindowIDBuf>0 )
{
WindowWidth = ui->HalconWinD->width();
WindowHeight = ui->HalconWinD->height();
SetWindowExtents(WindowIDBuf,0,0,WindowWidth,WindowHeight);
// DispObj(Image,WindowIDBuf);
}
}
//右键选取后的Menu的对应操作函数
void Widget::onTaskBoxContextMenuEvent()
{
QAction *pEven = qobject_cast<QAction *>(this->sender()); //this->sender()就是发信号者 QAction
int iType = pEven->data().toInt();
HTuple position;
GetDrawingObjectParams(selected_drawing_object,(HTuple("row1").Append("column1")),&position);
switch (iType)
{
case 1:
{
SetDrawingObjectParams(selected_drawing_object,"color","green");
QMessageBox::about(this, "tip", pEven->text());
break;
}
case 2:
{
SetDrawingObjectParams(selected_drawing_object,"color","blue");
QMessageBox::about(this, "tip", pEven->text());
break;
}
case 3:
{
SetDrawingObjectParams(selected_drawing_object,"color","yellow");
QMessageBox::about(this, "tip", pEven->text());
break;
}
case 4:
{
SetDrawingObjectParams(selected_drawing_object,"color","black");
QMessageBox::about(this, "tip", pEven->text());
break;
}
default:
break;
}
int Select_DrawID;
for(int i=0;i!=Drawing_Index.size();++i)
{
if(Drawing_Index.at(i) == selected_drawing_object)
{
Select_DrawID=i;
qDebug()<<"select ID:"<<i<<endl;
}
}
for(int i=0;i!=Drawing_Index.size();++i)
{
if(Drawing_Index.at(i) == selected_drawing_object)
{
Select_DrawID=i;
}
}
QString Message_test = pEven->text();
QByteArray ba = Message_test.toLocal8Bit();
const char *str = ba.data();
HTuple Draw_Message(str);
HTuple Draw_MesObj;
CreateDrawingObjectText(position[0],position[1], Draw_Message,&Draw_MesObj);
AttachDrawingObjectToWindow(WindowIDBuf,Draw_MesObj);
Draw_Text[Select_DrawID]=Draw_MesObj;
}
//右键选取删除操作对应函数
void Widget::onTaskDeleteObj()
{
int Select_DrawID;
for(int i=0;i!=Drawing_Index.size();++i)
{
if(Drawing_Index.at(i) == selected_drawing_object)
{
Select_DrawID=i;
qDebug()<<"select ID:"<<i<<endl;
}
}
if(Draw_Text.Length() >Select_DrawID)
{
DetachDrawingObjectFromWindow(WindowIDBuf,Draw_Text[Select_DrawID]);
DetachDrawingObjectFromWindow(WindowIDBuf,selected_drawing_object);
}
else
{
DetachDrawingObjectFromWindow(WindowIDBuf,selected_drawing_object);
}
}
//右键响应事件
void Widget::on_HalconWinD_customContextMenuRequested(const QPoint &pos)
{
HTuple Row_Mouse,Column_Mouse,Button,position;
GetMposition(WindowIDBuf,&Row_Mouse,&Column_Mouse,&Button);
GetDrawingObjectParams(selected_drawing_object,(HTuple("column1").Append("column2").Append("row1").Append("row2")),&position);
qDebug()<<Column_Mouse.D()<<Row_Mouse.D()<<position[0].D()<<position[1].D()<<position[2].D()<<position[3].D()<<endl;
if(Column_Mouse>position[0] && Column_Mouse<position[1])
{
if(Row_Mouse>position[2] && Row_Mouse<position[3])
{
//创建菜单对象
QMenu *pMenu = new QMenu(this);
QAction *pTask1 = new QAction(tr("得分王"), this);
QAction *pTask2 = new QAction(tr("总冠军"), this);
QAction *pTask3 = new QAction(tr("MVP"), this);
QAction *pTask4 = new QAction(tr("单场81分"), this);
QAction *action=new QAction(this);
QAction *pDelete = new QAction(tr("追随黑曼巴!"), this);
pTask1->setData(1);
pTask2->setData(2);
pTask3 ->setData(3);
pTask4->setData(4);
action->setSeparator(true);
pDelete ->setData(5);
//把QAction对象添加到菜单上
pMenu->addAction(pTask1);
pMenu->addAction(pTask2);
pMenu->addAction(pTask3);
pMenu->addAction(pTask4);
pMenu->addAction(action);
pMenu->addAction(pDelete);
//连接鼠标右键点击信号
connect(pTask1, SIGNAL(triggered()), this, SLOT(onTaskBoxContextMenuEvent()));
connect(pTask2, SIGNAL(triggered()), this, SLOT(onTaskBoxContextMenuEvent()));
connect(pTask3, SIGNAL(triggered()),this, SLOT(onTaskBoxContextMenuEvent()));
connect(pTask4, SIGNAL(triggered()), this, SLOT(onTaskBoxContextMenuEvent()));
connect(pDelete, SIGNAL(triggered()),this, SLOT(onTaskDeleteObj()));
//在鼠标右键点击的地方显示菜单
pMenu->exec(cursor().pos());
qDebug()<<cursor().pos().x()<<cursor().pos().y()<<endl;
//释放内存
QList<QAction*> list = pMenu->actions();
foreach (QAction* pAction, list) delete pAction;
delete pMenu;
}
}
}
//画矩形框
void Widget::on_btn_DrawRectangle_clicked()
{
HTuple Rect_ID;
CreateDrawingObjectRectangle1(100,100,200,200,&Rect_ID);
SetDrawingObjectParams(Rect_ID,"color","red");
qDebug()<<"Rect_ID"<<Rect_ID.D()<<endl;
Drawing_Index.append(Rect_ID);
//转换句柄为HDrawingObject
HDrawingObject draw=HDrawingObject(Rect_ID);
AttachDrawingObjectToWindow(WindowIDBuf,Rect_ID);
AttachDrawObj(draw);
}
void Widget::AttachDrawObj(HDrawingObject obj)
{
drawing_objects.append(obj);
obj.SetDrawingObjectCallback("on_resize",(void*)CallBackFunc_DrawObj);
obj.SetDrawingObjectCallback("on_drag",(void*)CallBackFunc_DrawObj);
// obj.SetDrawingObjectCallback("on_attach",CallBackFunc_Set);
obj.SetDrawingObjectCallback("on_select",(void*)CallBackFunc_Set);
// AttachDrawingObjectToWindow(ui->HalconWinD->WindowID(),obj);
}
//Drag和Resize对应的回调函数,这里用UI的一个自身指针将全局函数的变量传递给UI,从而调用UI下的函数
void CallBackFunc_DrawObj(long DrawID,long WindowHandle, char* type)
{
Widget::getInstance()->signal_data(DrawID);
}
//Drag和Resize对应的UI中的处理函数
void Widget::slot_ReceiveData(long DrawID)
{
int Select_DrawID;
for(int i=0;i!=Drawing_Index.size();++i)
{
if(Drawing_Index.at(i) == (HTuple)DrawID)
{
Select_DrawID=i;
qDebug()<<"delete ID:"<<i<<endl;
}
}
if(Draw_Text.Length() >Select_DrawID)
{
DetachDrawingObjectFromWindow(WindowIDBuf,Draw_Text[Select_DrawID]);
}
}
//选取矩形框对应的回调函数
void CallBackFunc_Set(long DrawID,long WindowHandle, char* type)
{
selected_drawing_object=DrawID;
SetDrawingObjectParams(DrawID,"color","blue");
HObject Region;
HTuple Area,row,column;
GetDrawingObjectIconic(&Region,DrawID);
AreaCenter(Region,&Area,&row,&column);
}
//清除窗口所有图形
void Widget::on_btn_ClearAllObj_clicked()
{
for(int i=0;i!=Drawing_Index.size();++i)
{
ClearDrawingObject(Drawing_Index.at(i));
}
for(int j=0;j<Draw_Text.Length();++j)
{
ClearDrawingObject(Draw_Text[j]);
}
Drawing_Index.clear();
Draw_Text=HTuple();
}
//画圆形
void Widget::on_btn_DrawCircle_clicked()
{
HTuple Circle_ID;
CreateDrawingObjectCircle(300,300,200,&Circle_ID);
SetDrawingObjectParams(Circle_ID,"color","red");
AttachDrawingObjectToWindow(WindowIDBuf,Circle_ID);
}
//画直线
void Widget::on_btn_DrawLine_clicked()
{
HTuple Line_ID;
CreateDrawingObjectLine(300,300,600,600,&Line_ID);
SetDrawingObjectParams(Line_ID,"color","yellow");
AttachDrawingObjectToWindow(WindowIDBuf,Line_ID);
}
5、总结
这个知识点本身并不难,而且Halcon也带有c#的例程,主要当初碰到的难点是无法理解其回调函数的Draw_ID是如何传递的,最后查到Halcon的帮助资料才发现,按照全局回调函数的样子去定义,回调会自动返回你当前所选择的Draw_ID,从而可以使用该Draw_ID进行你所需要的操作。
最后放上最终的效果: