通过VS2017和OpenCV,实现ids工业相机与电控位移台同步控制及数据采集
- 目录
- 项目环境配置
- 代码流程及思路
- 项目架构
- 项目开发运行效果
- 开发关键
- ids相机配置
- 位移台环境配置
- 相机头文件
- 相机参数设置
- 保存图像函数设置
- 电控位移台头文件
- 电控位移台设置参数
- 最后就是通过main函数进行调用和控制,需要注意的是,最好能加上sleep函数,这样采集图像不容易出问题
目录
光学数据采集系统中,经常需要采集几万个样本点进行数据分析,如果通过人工数据采集,人工成本太高,同时容易出现测量误差等因素。因此需要根据项目功能开发一套自动化采集图像和位移控制的工程项目,以下是具体内容:
项目环境配置
代码环境:
VS2017
OpenCV3.4.2
ids开发包,包括.lib、.dll、.h文件等
LBtek电控五相位移平台,.dll、.h文件
代码流程及思路
- 相机初始化,根据图像尺寸申请内存地址;
- 导出图像内存指针,设置传感器参数
- 初始化位移台,新建类
- 通过dll查找位移台函数指针,调用函数
- 设置边界条件及步进电机参数
- 通过函数返回值与延时等机制,实现相机同步
- 批量化图像采集与数据处理
项目架构
项目开发运行效果
开发关键
相机和位移台属于商业产品,商家不提供完整程序包,只给出dll动态链接库,需要通过函数指针调用接口。
通过算法自定义位移台运行模式;
提升图像增益及伽马校正,自定义图像采集AOI,提升采集帧率;
ids相机配置
本文是基于debug x64模式下,C++ 环境运行,需要一老OpenCV的部分库函数,进行图像展示和保存。
OpenCV环境配置:具体配置方法CSDN其他博客都有记录,是通用的,
比如我之前配置旧版本的环境,大家可以参考: link
;
大家要注意,将这里的语言 符合模式 设置为否,原因是ids相机开发包里面存储文件的时候,使用的是wchat_t *的格式,调试过程中很容易出问题。如下图设置
相机环境配置完成。
位移台环境配置
由于开发包里面只提供了配置文件、.dll文件、.h文件,函数实现方法是不知道的,因此调用函数的时候是将这三个文件放置到项目代码包中,通过函数指针调用函数,配置说明书摸索函数功能。
这一部分环境配置就是将这几个文件放置到工程项目文件夹中即可。
相机头文件
#include "ueye.h"
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>class Idscam {public:Idscam();INT InitCamera(HIDS *hCam, HWND hWnd); //初始化相机 hWnd指向显示图像窗口的指针,若用DIB模式可以令hWnd=NULL bool OpenCamera();void ExitCamera();int InitDisplayMode();void GetMaxImageSize(INT *pnSizeX, INT *pnSizeY);//查询相机支持的图像格式void SaveImage(wchar_t* fileName);bool GetiplImgFormMem(); //从视频数据流中将图像数据拷贝给IplImage// uEye variblesHIDS m_hCam; // 相机句柄HWND m_hWndDisplay; // window显示句柄INT m_nColorMode; // Y8/RGB16/RGB24/REG32INT m_nBitsPerPixel; // 图像位深INT m_nSizeX; // 图像宽度INT m_nSizeY; // 图像高度INT m_nPosX; // 图像左偏移INT m_nPosY; // 图像右偏移cv::Mat CamMat;IplImage *iplImg;char *m_pLastBuffer;private:// 使用位图模式进行实时显示需要的内存 INT m_lMemoryId; // camera memory - buffer IDchar* m_pcImageMemory; // camera memory - pointer to bufferSENSORINFO m_sInfo; // sensor information structINT m_nRenderMode; // render modeINT m_nFlipHor; // 水平翻转标志INT m_nFlipVert; // 垂直翻转标志};
相机参数设置
int SetParasofCamera(HIDS hCam, uint nPixelClockSet, double nFramePerSecondSet, double nExposureTimeSet) {// 像素时钟查询与设置UINT nPixelClockDefault, nPixelClockCurrent;INT nRet;/*// 获取默认像素时钟nRet = is_PixelClock(hCam, IS_PIXELCLOCK_CMD_GET_DEFAULT, (void*)&nPixelClockDefault, sizeof(nPixelClockDefault));if (nRet == IS_SUCCESS) {cout << "Success, and default pixel clock is : " << nPixelClockDefault << " MHz." << endl;}else {cout << "Failed to get default pixel clock" << endl;return IS_NO_SUCCESS;}*/// 设置该像素时钟//nPixelClockSet = 86; //设置为80MHznRet = is_PixelClock(hCam, IS_PIXELCLOCK_CMD_SET, (void*)&nPixelClockSet, sizeof(nPixelClockSet));if (nRet == IS_SUCCESS) {cout << "Success, and user set pixel clock is : " << nPixelClockSet << " MHz." << endl;}else {cout << "Failed to set pixel clock" << endl;return IS_NO_SUCCESS;}// 获取当前像素时钟nRet = is_PixelClock(hCam, IS_PIXELCLOCK_CMD_GET, (void*)&nPixelClockCurrent, sizeof(nPixelClockCurrent));if (nRet == IS_SUCCESS) {cout << "Success, and current pixel clock is : " << nPixelClockCurrent << " MHz." << endl;}else {cout << "Failed to get current pixel clock" << endl;return IS_NO_SUCCESS;}cout << "***********************************************************************************" << endl;/*// 相机帧率范围查询与设置double nFrameMin, nFrameMax, nFrameIntervall;nRet = is_GetFrameTimeRange(hCam, &nFrameMin, &nFrameMax, &nFrameIntervall);if (nRet == IS_SUCCESS) {cout << "Success, Min Frame = " << 1 / nFrameMax << "fps, Max Frame = " << 1 / nFrameMin << "fps, and Frame Intervall = " << nFrameIntervall << "fps." << endl;}else {cout << "Failed to get nFrameMin, nFrameMax, nFrameIntervall." << endl;return IS_NO_SUCCESS;}*/// 设置、并查询 当前相机采集帧率double nFramesPerSecondCurrent;nRet = is_SetFrameRate(hCam, nFramePerSecondSet, &nFramesPerSecondCurrent);if (nRet == IS_SUCCESS) {cout << "Success, user set frame is: " << nFramePerSecondSet << "fps, and current frame is : " << (nFramesPerSecondCurrent) << "fps" << endl;}else {cout << "Failed to get current frame" << endl;return IS_NO_SUCCESS;}cout << "***********************************************************************************" << endl;// 曝光时间查询与设置double nExposureTimeDefault, nExposureTimeCurrent;/* // 获取曝光时间范围查询 这个通过和IDS程序对比,是对的//Success, and exposure time range is : min = 0.00898246 ms, max = 19.9832ms, delta = 0.0158596ms. IS_EXPOSURE_CMD_GET_EXPOSURE_RANGE//Success, and exposure time range is : min = 0.00898246 ms, max = 19.9832ms, delta = 0.000140351ms. IS_EXPOSURE_CMD_GET_FINE_INCREMENT_RANGEdouble nExposureTimeRange;double dblRange[3];nRet = is_Exposure(hCam, IS_EXPOSURE_CMD_GET_FINE_INCREMENT_RANGE, (void*)dblRange, sizeof(dblRange));if (nRet == IS_SUCCESS) {cout << "Success, and exposure time range is : min = " << (dblRange[0]) << " ms, max = "<< (dblRange[1]) << "ms, delta = " << (dblRange[2]) << "ms." << endl;}else {cout << "Failed to get exposure time range" << endl;return IS_NO_SUCCESS;}// 获取默认曝光时间nRet = is_Exposure(hCam, IS_EXPOSURE_CMD_GET_EXPOSURE_DEFAULT, &nExposureTimeDefault, sizeof(nExposureTimeDefault));if (nRet == IS_SUCCESS) {cout << "Success, and default exposure time is : " << nExposureTimeDefault << " ms." << endl;}else {cout << "Failed to get default exposure time" << endl;return IS_NO_SUCCESS;}*/// 设置曝光时间(单位:ms)nRet = is_Exposure(hCam, IS_EXPOSURE_CMD_SET_EXPOSURE, &nExposureTimeSet, sizeof(nExposureTimeSet));if (nRet == IS_SUCCESS) {cout << "Success, and set exposure time is : " << nExposureTimeSet << " ms." << endl;}else {cout << "Failed to set exposure time" << endl;return IS_NO_SUCCESS;}// 再次获取曝光时间,确保设置成功nRet = is_Exposure(hCam, IS_EXPOSURE_CMD_GET_EXPOSURE, &nExposureTimeCurrent, sizeof(nExposureTimeCurrent));if (nRet == IS_SUCCESS) {cout << "Success, and current exposure time is : " << nExposureTimeCurrent << " ms." << endl;}else {cout << "Failed to get current exposure time" << endl;return IS_NO_SUCCESS;}cout << "***********************************************************************************" << endl;return IS_SUCCESS;
}
保存图像函数设置
int SaveIdsImage(HIDS hCam, wchar_t* fileName) {IMAGE_FILE_PARAMS ImageFileParams;// 每次保存图像之前,都要先进行清空指针ImageFileParams.pwchFileName = NULL;ImageFileParams.pnImageID = NULL;ImageFileParams.ppcImageMem = NULL;ImageFileParams.nQuality = 0;//保存活动内存中的bmp图像,图像画质为80(不含”打开文件”对话框)ImageFileParams.pwchFileName = fileName; // L"test.bmp";ImageFileParams.nFileType = IS_IMG_BMP;ImageFileParams.nQuality = 80;int nRet = is_ImageFile(hCam, IS_IMAGE_FILE_CMD_SAVE, (void*)&ImageFileParams, sizeof(ImageFileParams));return nRet;
}
电控位移台头文件
#ifndef MOVERLIBRARY_H
#define MOVERLIBRARY_H#ifdef __cplusplus
#ifdef MOVER_LIBRARY
#define DLL_EXPORT extern "C" __declspec( dllexport )
#else
#define DLL_EXPORT extern "C" __declspec( dllimport )
#endif
#else
#define DLL_EXPORT
#endif#define MOVE_CODE_STOP 0x01 // 停止
#define MOVE_CODE_RESTORE 0x02 // 回原点
#define MOVE_CODE_DRIVE_R 0x04 // 向右相对运动
#define MOVE_CODE_DRIVE_L 0x05 // 向左相对运动
#define MOVE_CODE_MOVE 0x06 // 运动到指定位置
#define MOVE_CODE_JOG_R 0x07 // 向右步进
#define MOVE_CODE_JOG_L 0x08 // 向左步进DLL_EXPORT int listPorts(char *serialNo, unsigned int len); // 列举串口设备
DLL_EXPORT int open(char *serialNo); // 打开串口设备
DLL_EXPORT int isOpen(char *serialNo); // 检查串口设备是否已打开
DLL_EXPORT int close(int handle); // 关闭串口设备
DLL_EXPORT int read(int handle, char *data, int len); // 读取数据
DLL_EXPORT int write(int handle, char *data, int len); // 写入数据// 设置D寄存器
DLL_EXPORT int move(int handle, int ID, int func); // 运行
DLL_EXPORT int setSpeed(int handle, int ID, float speed); // 设置速度
DLL_EXPORT int setAcceleration(int handle, int ID, float Acc); // 设置加速度
DLL_EXPORT int setAbsoluteDisp(int handle, int ID, float disp); // 设置绝对位移量
DLL_EXPORT int setRelativeDisp(int handle, int ID, float disp); // 设置相对位移量
DLL_EXPORT int setJogTime(int handle, int ID, int time); // 设置步进时间
DLL_EXPORT int setJogStep(int handle, int ID, float step); // 设置步进步长
DLL_EXPORT int setJogDelay(int handle, int ID, int delay); // 设置步进延迟DLL_EXPORT int setInputEnable(int handle, int ID, int enable); // 输入有效
DLL_EXPORT int setOutputEnable(int handle, int ID, int enable); // 输出有效
DLL_EXPORT int setAxisEnable(int handle, int ID, int enable); // 轴使能
DLL_EXPORT int setRelativePosEnable(int handle, int ID, int enable); // 相对位置值使能// 只读
DLL_EXPORT int getDoingState(int handle, int ID); // 运动状态
DLL_EXPORT int getPositiveLimitEnable(int handle, int ID); // 限位+
DLL_EXPORT int getNegativeLimitEnable(int handle, int ID); // 限位-
DLL_EXPORT int getOriginEable(int handle, int ID); // 原点
DLL_EXPORT int getDeviceCode(int handle); // 控制器设备型号(支持的轴数量)// 获取寄存器值
DLL_EXPORT float getSpeed(int handle, int ID); // 速度
DLL_EXPORT float getAcceleration(int handle, int ID); // 加速度
DLL_EXPORT float getAbsoluteDisp(int handle, int ID); // 绝对位移量
DLL_EXPORT float getRelativeDisp(int handle, int ID); // 相对位移量
DLL_EXPORT int getJogTime(int handle, int ID); // 步进时间
DLL_EXPORT float getJogStep(int handle, int ID); // 步进步长
DLL_EXPORT int getJogDelay(int handle, int ID); // 步进延迟DLL_EXPORT int getAxisType(int handle, int ID); // 轴类型
DLL_EXPORT float GetCurrentPos(int handle, int ID, int *ok);// 当前位置
DLL_EXPORT int getInputEnable(int handle, int ID); // 输入有效
DLL_EXPORT int getOutputEnable(int handle, int ID); // 输出有效
DLL_EXPORT int getAxisEnable(int handle, int ID); // 轴使能
DLL_EXPORT int getRelativePosEnable(int handle, int ID); // 相对位置值使能,未使能时,drive不需要设置位移量DLL_EXPORT int getAllModels(char* modelName, int len); // 获取所有位移台型号名称用","隔开
DLL_EXPORT int initAxis(int handle, int ID, char* model, int AxisCount); // 初始化轴
DLL_EXPORT int getErrorCode(int handle, int ID); // 获取错误代码#endif // MOVERLIBRARY_H
电控位移台设置参数
// 位移台运动函数设置:
int LBtekMoverParaSet() {cout << "***************** Loading moverLibrary_x64.dll, Get and Init Mover Device. *******************" << endl;// 运行时加载DLL库HMODULE dllHandle = LoadLibrary(L"moverLibrary_x64.dll");if (dllHandle == NULL){printf("加载DLLTest1.dll动态库失败\n");return -1;}// 获取listPorts函数地址MyFunctionType1 GetMoverSerialList = (MyFunctionType1)GetProcAddress(dllHandle, "listPorts");/*if (GetMoverSerialList == NULL) {std::cout << "Failed to get function address." << std::endl;return -1;}*/// 通过GetMoverSerialList调用函数listPorts ret = GetMoverSerialList(serialList, 1024); // 获取当前电脑所有已连接的串口设备名称列表 我们的应该是COM5if (ret >= 0) {cout << "Success to get Serial List:" << serialList << std::endl;}else {cout << "Failed to get Serial List!" << std::endl;return -1;}// 获取位移台型号名称列表 我们的应该是 EM-LSS65-50C1MyFunctionType1 GetMoverModelsList = (MyFunctionType1)GetProcAddress(dllHandle, "getAllModels");ret = GetMoverModelsList(modelsList, 1024);if (ret >= 0) {cout << "Success to get Mover Model List:" << modelsList << std::endl;}else {cout << "Failed to get Mover Model List!" << std::endl;return -1;}// 打开串口设备 MyFunctionType2 OpenMoverEmcvx = (MyFunctionType2)GetProcAddress(dllHandle, "open"); // 打开串口设备int open(char *serialNo); m_handle = OpenMoverEmcvx(name);if (ret > 0) {cout << "Success to Open Emcvx." << endl;}else {cout << "Failed to Open Emcvx." << endl;return -1;}/*// 检查串口设备是否正确打开MyFunctionType2 CheckMoverEmcvxState = (MyFunctionType2)GetProcAddress(dllHandle, "isOpen"); //if (CheckMoverEmcvxState(name) > 0) {cout << "Success to Open Emcvx.(Check)" << endl;}else {cout << "Failed to Open Emcvx.(Check)" << endl;return -1;}*/// 初始化轴设备 Axis1MyFunctionType7 InitMoverAxis = (MyFunctionType7)GetProcAddress(dllHandle, "initAxis");MyFunctionType8 GetMoverDeviceCode = (MyFunctionType8)GetProcAddress(dllHandle, "getDeviceCode");int axisCount = GetMoverDeviceCode(m_handle); // 获取轴设备数量cout << "Axis Count = " << axisCount << endl;if (InitMoverAxis(m_handle, ID, modelName, axisCount) >= 0) {cout << "Success to Init Mover Axis 1." << endl;}else {cout << "Failed to Init Mover Axis 1." << endl;return -1;}cout << "************************************ Setting Device Paras. ******************************" << endl;// 设置运行速度MyFunctionType5 SetMoverSpeed = (MyFunctionType5)GetProcAddress(dllHandle, "setSpeed");if (SetMoverSpeed(m_handle, ID, speed) == 0) {cout << "Success to Set Mover Speed at " << speed << endl;}else {cout << "Failed to Set Mover Speed." << endl;return -1;}// 设置运行加速度MyFunctionType5 SetMoverAcceleration = (MyFunctionType5)GetProcAddress(dllHandle, "setAcceleration");if (SetMoverAcceleration(m_handle, ID, Acc) == 0) {cout << "Success to Set Mover Acc at " << Acc << endl;}else {cout << "Failed to Set Mover Acc." << endl;return -1;}// 设置运行相对位移量MyFunctionType5 SetMoverRelativeDisp = (MyFunctionType5)GetProcAddress(dllHandle, "setRelativeDisp");if (SetMoverRelativeDisp(m_handle, ID, disp) == 0) {cout << "Success to Set Mover Relative Disp at " << disp << "mm" << endl;}else {cout << "Failed to Set Mover Relative Disp." << endl;return -1;}// 设置步进次数MyFunctionType4 SetMoverJogTime = (MyFunctionType4)GetProcAddress(dllHandle, "setJogTime");if (SetMoverJogTime(m_handle, ID, times) == 0) {cout << "Success to Set Mover Jog Times at " << times << endl;}else {cout << "Failed to Set Mover Jog Times." << endl;return -1;}// 设置步进步长MyFunctionType5 SetMoverJogStep = (MyFunctionType5)GetProcAddress(dllHandle, "setJogStep");if (SetMoverJogStep(m_handle, ID, step) == 0) {cout << "Success to Set Mover Jog Step at " << speed << endl;}else {cout << "Failed to Set Mover Jog Step." << endl;return -1;}// 设置步进延时MyFunctionType4 SetMoverJogDelay = (MyFunctionType4)GetProcAddress(dllHandle, "setJogDelay");if (SetMoverJogDelay(m_handle, ID, times) == 0) {cout << "Success to Set Mover Jog Delay at " << delay << "ms" << endl;}else {cout << "Failed to Set Mover Jog Delay." << endl;return -1;}// 设置位移台轴使能MyFunctionType4 SetMoverAxisEnable = (MyFunctionType4)GetProcAddress(dllHandle, "setAxisEnable");if (SetMoverAxisEnable(m_handle, ID, 0x01) == 0) { // 0x00未使能; 0x01使能cout << "Success to Enable Mover." << endl;}else {cout << "Failed to Enable Mover." << endl;return -1;}// 获取位移台运行状态MyFunctionType6 GetMoverDoingState = (MyFunctionType6)GetProcAddress(dllHandle, "getDoingState");ret = GetMoverDoingState(m_handle, ID);if (ret == 0x01) {cout << "Success to get the state of Mover, and it is running." << endl;}else if (ret == 0) {cout << "Success to get the state of Mover, but it isn't running." << endl;}else {cout << "Failed to get the state of Mover!" << endl;return -1;}// 获取位移台当前位置 float GetCurrentPos(int handle, int ID, int *ok);// 当前位置MyFunctionType3 GetMoverCurrentPos = (MyFunctionType3)GetProcAddress(dllHandle, "GetCurrentPos");int signOK = 0;currentPos = GetMoverCurrentPos(m_handle, ID, &signOK);if (currentPos >= 0) { // 0x00未使能; 0x01使能cout << "Success to Get Mover Current Position at " << currentPos << " mm" << endl;}else {cout << "Failed to Get Mover Current Position." << endl;return -1;}// 获取位移台正方向限位状态MyFunctionType6 GetMoverPositiveLimitEnable = (MyFunctionType6)GetProcAddress(dllHandle, "getPositiveLimitEnable");int limitMaxState = GetMoverPositiveLimitEnable(m_handle, ID);if (limitMaxState == 0x01) {cout << "Warrning! It is Located in limitMaxState!" << endl;}else if (ret == 0) {cout << "Success to check, it isn't at limitMaxState." << endl;}else {cout << "Failed to get the state of limitMaxState!" << endl;return -1;}// 获取位移台负方向限位状态MyFunctionType6 GetMoverNegativeLimitEnable = (MyFunctionType6)GetProcAddress(dllHandle, "getNegativeLimitEnable");int limitMinState = GetMoverNegativeLimitEnable(m_handle, ID);if (limitMinState == 0x01) {cout << "Warrning! It is Located in limitMinState!" << endl;}else if (ret == 0) {cout << "Success to check, it isn't at limitMinState." << endl;}else {cout << "Failed to get the state of limitMinState!" << endl;return -1;}// 使能位移台运动MyFunctionType4 MoverEnable = (MyFunctionType4)GetProcAddress(dllHandle, "move"); // int move(int handle, int ID, int func); /*// func: 0x01停止 0x02回原点 0x04正方向推进 0x05负方向推进 0x06移动到指定位置 0x07正反向步进 0x08负方向步进ret = MoverEnable(m_handle, ID, 0x07);if (ret == 0) {cout << "Success, Jog move." << endl;}else {cout << "Failed to Jog Move!" << endl;return -1;}*/// 卸载DLL文件 FreeLibrary(dllHandle);return 0;
}
最后就是通过main函数进行调用和控制,需要注意的是,最好能加上sleep函数,这样采集图像不容易出问题
如果有更多需求,欢迎大家私信交流。