1、概述
机器视觉我的项目中,如何采集到适合的图像是我的项目的第一步,也是最重要的一步,间接关系到前面图像处理算法及最终执行的后果。所以采纳不同的工业相机成像以及如何转换成图像处理库所须要的格局成为我的项目开发中首先要思考的问题。
2、工业相机图像采集形式
这里工业相机抉择就不赘述了,因为绝对于我的项目或设施来讲,须要依据我的项目须要筛选适宜的硬件,而针对图像采集也有基于 SDK 开发或间接采纳 Halcon 的相机采集助手。
2.1 相机自带的 SDK 采图
这种形式是厂家比拟举荐的,也是比拟罕用的办法,起因之一是每个厂家对本人的产品比拟熟知,提供的 SDK 也是比较稳定的,而且也会有肯定的技术支持;第二个起因就是基于 SDK 的开发能够获取更多相机及图像的原始参数,这样在做我的项目中的灵便度也会更高,个别大一些的品牌相机的 SDK 和 demo 程序会更具体,而且针对相机封装的函数也更欠缺,这里我在我的项目中用的较多的是 Dalsa 和 basler,国产的用的较多的是海康、京航和方诚。这里针对 Dalsa 和 balsa 的面阵相机做一下简略的介绍。(编程环境是 Qt5)
2.1.1 Dalsa 相机采图形式
初始化相机
void CaptureThread::initCamera()
{m_AcqDevice = new SapAcqDevice(SapLocation("Nano-M2420_1", 0),false);
BOOL Status = m_AcqDevice->Create();
if(Status){qDebug()<<"相机连贯胜利"<<endl;
m_Buffers = new SapBufferWithTrash(3,m_AcqDevice);
m_AcqDevice->GetFeatureValue("Width",&Img_Width);
m_AcqDevice->GetFeatureValue("Height",&Img_Height);
// qDebug()<<"Width:"<<Img_Width<<"Height:"<<Img_Height<<endl;
m_Xfer = new SapAcqDeviceToBuf(m_AcqDevice,m_Buffers,XferCallback,this);
}
if (!CreateObjects()) {return;}
}
回调函数次要是用于获取图像数据
void CaptureThread::XferCallback(SapXferCallbackInfo *pInfo)
{CaptureThread *pDlg = (CaptureThread *) pInfo->GetContext();
if(pInfo->IsTrash()){qDebug()<<"IsTrash"<<endl;
return;
}
// qDebug()<<"相机向缓存中写入数据"<<endl;
HObject hv_Current_Image;
BYTE *pData;
pDlg->m_Buffers->GetAddress((void**)&pData);
GenImage1(&hv_Current_Image,"byte",pDlg->Img_Width,pDlg->Img_Height,(Hlong)pData);
// GenImageInterleaved(&hv_Current_Image,(Hlong)pData,"rgb",pDlg->Img_Width,pDlg->Img_Height,-1,"byte",0,0,0,0,-1,0);
// 判断图像处理工作形式
if(pDlg->TestImg_WorkType == 0)
{emit pDlg->startImgProcess(hv_Current_Image);
pDlg->TestImg_WorkType = -1;
}
else if(pDlg->TestImg_WorkType == 1)
{emit pDlg->startImgProcess(hv_Current_Image);
}
else{emit pDlg->canShowImg(hv_Current_Image);
}
pDlg->m_Buffers->ReleaseAddress((void *)pData);
pDlg->m_Buffers->Clear(pDlg->m_Buffers->GetIndex());
}
最初是调用 Dalsa 相机的采图函数来触发
void CaptureThread::OnGrab()
{m_Xfer->Grab();
}
void CaptureThread::OnSnap()
{m_Xfer->Snap();
}
void CaptureThread::OnFreeze()
{
SnapSignal = true;
m_Xfer->Freeze();}
2.1.2 basler 相机采图形式
首先初始化相机:
void CBaslerCameraControl::initCamera()
{PylonInitialize(); // 初始化相机
m_basler.RegisterImageEventHandler(this, RegistrationMode_Append, Cleanup_Delete); // 注册图像事件程序,模式为 Append 插入;// m_basler.RegisterConfiguration(this, RegistrationMode_ReplaceAll, Cleanup_None); // 注册图像事件,模式为单张笼罩;m_basler.Attach(CTlFactory::GetInstance().CreateFirstDevice(),Cleanup_Delete);
qDebug()<<"Using device" << m_basler.GetDeviceInfo().GetModelName()<<endl;
m_basler.Open(); // 关上相机
if (!m_basler.IsOpen() || m_basler.IsGrabbing())
{qDebug()<<"camera open failed"<<endl;
return;
}
}
下一步就能够调用库函数进行采集
// 间断采图的回调函数
void CBaslerCameraControl::OnImageGrabbed(CInstantCamera &camera, const CGrabResultPtr &grabResult)
{m_mutexLock.lock();
if (grabResult->GrabSucceeded())
{
m_ptrGrabResult = grabResult;// 将捕捉到的图像传递进来
// qDebug() <<"Captureok"<<endl;
HObject hv_CurrentImg;
CopyImgToHObject(m_ptrGrabResult,hv_CurrentImg);
emit canShowImg(hv_CurrentImg);
qDebug() <<"Captureok"<<endl;}
m_mutexLock.unlock();}
void CBaslerCameraControl::StartAcquire()
{if ( !m_basler.IsGrabbing() ){
GrabOnLine_Signal = true;
m_basler.StartGrabbing(GrabStrategy_LatestImageOnly,GrabLoop_ProvidedByInstantCamera);
}
}
void CBaslerCameraControl::StartSnap()
{m_basler.StartGrabbing(1);
CBaslerUniversalGrabResultPtr ptrGrabResult;
m_basler.RetrieveResult(5000, ptrGrabResult, TimeoutHandling_ThrowException);
if (ptrGrabResult->GrabSucceeded())
{qDebug()<<"snapok"<<endl;
HObject hv_CurrentImg;
CopyImgToHObject(ptrGrabResult,hv_CurrentImg);
emit canShowImg(hv_CurrentImg);
}
}
void CBaslerCameraControl::CloseCamera()
{if(m_basler.IsOpen()) {m_basler.DetachDevice();
m_basler.Close();
m_basler.DestroyDevice();
m_ptrGrabResult.Release();}
}
void CBaslerCameraControl::deleteAll()
{
// 进行采集
if(m_isOpenAcquire) {StopAcquire();
}
// 敞开摄像头
try
{CloseCamera();
m_basler.DeregisterImageEventHandler(this);
// 敞开库
qDebug() << "SBaslerCameraControl deleteAll: PylonTerminate" ;
PylonTerminate();}
catch (const Pylon::GenericException& e)
{qDebug() << e.what();}
}
2.1.3 小结
大部分的相机 SDK 大体都相似,都是通过相机句柄去调用图像采集的回调函数或图像 buff,在以上介绍的程序中 SDK 获取的是图像的 buff,再通过 buff 里的图像数据转换成 HObject 或者 Mat 格局,这个具体操作下节再讲。通过相机 SDK 进行 Grab 或 Snap,其劣势是成像高效稳固,搞懂相机自带的 SDK 程序和 Demo 程序,能够很快的实现采图测试,而且 BYTE 格局的原始图像数据,能够应用 c ++ 进行 paint 或转成 Qt 的 Qimage 进行显示操作。其不便之处就在于,将 BYTE 转换成 HObject 或 Mat 的耗时,可能会影响图像实时显示,所以须要思考图像显示和解决的效率问题。对于有编程根底的同学,举荐应用此办法进行图像采集。
2.2 Halcon 自带的图像采集助手进行采图
1. 应用 basler 相机自带的 IPConfigurator 软件设置好电脑的 ip 地址,保障电脑和相机已连贯胜利,而后应用 basler 的采图工具 pylon viewer 测试是否能采集到图像;
2. 关上 halcon,助手选项下抉择“关上新的 Image Acquisition”
3. 自动检测接口,抉择对应的相机接口文件,如“pylon”,而后点击“代码生成”即可
2.3 采图办法总结
应用 Halcon 采图助手进行采图比拟适宜老手,没有太大的编程量,也能够用于项目前期的计划制订,图像处理评估等;第一种采图办法的适用性更广,能够满足不同我的项目的开发需要。
3、图像格式的相互转换(BYTE、HObject、Mat 和 QImage)
在我的项目开发过程中,会用到不同的图像处理库、不同的图形显示环境,所以须要针对图像的格局进行对应的转换
3.1 BYTE 转 HObject
这个比拟罕用,通过相机 SDK 采集后的图像数据转换成 Halcon 格局,便于前期的图像处理操作;
void CBaslerCameraControl::CopyImgToHObject(CGrabResultPtr pInBuffer, HObject &hv_Image)
{HBYTE *pData = (HBYTE*)pInBuffer->GetBuffer();
int nHeight = pInBuffer->GetHeight();
int nWidth = pInBuffer->GetWidth();
GenImage1(&hv_Image,"byte",nWidth,nHeight,(Hlong)pData);
// HObject hv_Current_Image;
// // 图像格式转换方法一:数据拷贝
// // int size = (pDlg->Img_Width)* (pDlg->Img_Height)*sizeof(BYTE);
// // BYTE *dataBuf = new BYTE[size];
// // dataBuf = (byte *)malloc(size);
// // pDlg->m_Buffers->Read(0,(pDlg->Img_Width)* (pDlg->Img_Height),dataBuf);
// // GenImage1(&hv_Current_Image,"byte",pDlg->Img_Width,pDlg->Img_Height,(Hlong)dataBuf);
// // 图像格式转换方法二:数据指针
// BYTE *pData;
// pDlg->m_Buffers->GetAddress((void**)&pData);
// GenImage1(&hv_Current_Image,"byte",pDlg->Img_Width,pDlg->Img_Height,(Hlong)pData);
// // GenImageInterleaved(&hv_Current_Image,(Hlong)pData,"rgb",pDlg->Img_Width,pDlg->Img_Height,-1,"byte",0,0,0,0,-1,0);
}
上述转换有两种办法,一种应用数据拷贝,一种应用数据指针,相比拟第二种耗时少,举荐应用。
3.2 BYTE 转 Mat、QImage
void CBaslerCameraControl::CopyImgToMat(CGrabResultPtr pInBuffer, Mat &Mat_Img)
{HBYTE *pData = (HBYTE*)pInBuffer->GetBuffer();
int nHeight = pInBuffer->GetHeight();
int nWidth = pInBuffer->GetWidth();
unsigned char *pImageBuffer = (unsigned char *)pInBuffer->GetBuffer();
// 黑白图像
const uint8_t *pImageBuffer = (uint8_t *) ptrGrabResult->GetBuffer();
Mat_img =Mat(cv::Size(nWidth , nHeight), CV_8U, (void*)pImageBuffer, cv::Mat::AUTO_STEP);
QImg = QImage(pImageBuffer ,nWidth ,nHeight ,QImage::Format_Indexed8);
// 彩色图像
// 新建 pylon ImageFormatConverter 对象.
CImageFormatConverter formatConverter;
// 确定输入像素格局
formatConverter.OutputPixelFormat = PixelType_BGR8packed;
// 将抓取的缓冲数据转化成 pylon image.
formatConverter.Convert(m_bitmapImage, pInBuffer);
// 将 pylon image 转成 OpenCV image.
Mat_img = cv::Mat(pInBuffer->GetHeight(), pInBuffer->GetWidth(), CV_8UC3, (uint8_t *)m_bitmapImage.GetBuffer());
QImg = QImage(pImageBuffer ,nWidth /3,nHeight ,nWidth ,QImage::Format_RGB888);
}
3.3 HObject 与 Mat 互转
using namespace cv;
using namespace Halcon;
//HObject 转 Mat
Mat HObject2Mat(HObject Hobj)
{HTuple htCh = HTuple();
HString cType;
cv::Mat Image;
ConvertImageType(Hobj,&Hobj,"byte");
CountChannels(Hobj,&htch);
Hlong wid = 0;
Hlong hgt = 0;
if(htch[0].I() == 1)
{HImage hImg(Hobj);
void *ptr = hImg.GetImagePointer1(&cType,&wid,&hgt);
int W = wid;
int H = hgt;
Image.create(H,W,CV_8UC1);
unsigned char *pdata = static_case<unsigned char*>(ptr);
memcpy(Image.data,pdata,W*H);
}
else if (htch[0].I() == 3)
{
void *Rptr;
void *Gptr;
void *Bptr;
HImage hImg(Hobj);
hImg.GetImagePointer3(&Rptr,&Gptr,&Bptr,&cType,&wid,&hgt);
int W = wid;
int H = hgt;
Image.create(H,W,CV_8UC3);
vector<cv::Mat> VecM(3);
VecM[0].create(H,W,CV_8UC1);
VecM[1].create(H,W,CV_8UC1);
VecM[2].create(H,W,CV_8UC1);
unsigned char *R = (unsigned char *)Rptr;
unsigned char *G = (unsigned char *)Gptr;
unsigned char *B = (unsigned char *)Bptr;
memcpy(VecM[2].data,R,W*H);
memcpy(VecM[1].data,G,W*H);
memcpy(VecM[0].data,B,W*H);
cv::merge(VecM,Image);
}
return Image;
}
//Mat 转 Hobject
Hobject Mat2Hobject(Mat& image)
{Hobject Hobj = Hobject();
int hgt = image.rows;
int wid = image.cols;
int i;
if (image.type() == CV_8UC3)
{
vector<Mat> imgchannel;
split(image, imgchannel);
Mat imgB = imgchannel[0];
Mat imgG = imgchannel[1];
Mat imgR = imgchannel[2];
uchar* dataR = new uchar[hgt*wid];
uchar* dataG = new uchar[hgt*wid];
uchar* dataB = new uchar[hgt*wid];
for (i = 0; i < hgt; i++)
{memcpy(dataR + wid*i, imgR.data + imgR.step*i, wid);
memcpy(dataG + wid*i, imgG.data + imgG.step*i, wid);
memcpy(dataB + wid*i, imgB.data + imgB.step*i, wid);
}
gen_image3(&Hobj, "byte", wid, hgt, (Hlong)dataR, (Hlong)dataG, (Hlong)dataB);
delete[]dataR;
delete[]dataG;
delete[]dataB;
dataR = NULL;
dataG = NULL;
dataB = NULL;
}
else if (image.type() == CV_8UC1)
{uchar* data = new uchar[hgt*wid];
for (i = 0; i < hgt; i++)
memcpy(data + wid*i, image.data + image.step*i, wid);
gen_image1(&Hobj, "byte", wid, hgt, (Hlong)data);
delete[] data;
data = NULL;
}
return Hobj;
}
3.4 HObject 转 QImage
void HObjectToQImage(HObject hv_image, QImage &qimage)
{
HTuple hChannels,htype,hpointer;
HTuple width=0;
HTuple height=0;
ConvertImageType(hv_image,&hv_image,"byte");// 将图片转化成 byte 类型
CountChannels(hv_image,&hChannels); // 判断图像通道数
if(hChannels[0].I()==1)// 单通道图
{
unsigned char *ptr;
GetImagePointer1(hv_image,&hpointer,&htype,&width,&height);
ptr=(unsigned char *)hpointer[0].L();
qimage= QImage(ptr,width,height,QImage::Format_Indexed8);
}
else if(hChannels[0].I()==3)// 三通道图
{
unsigned char *ptr3;
HObject ho_ImageInterleaved;
rgb3_to_interleaved(hv_image, &ho_ImageInterleaved);
GetImagePointer1(ho_ImageInterleaved, &hpointer, &htype, &width, &height);
ptr3=(unsigned char *)hpointer[0].L();
qimage= QImage(ptr3,width/3,height,width,QImage::Format_RGB888);
}
}
void rgb3_to_interleaved (HObject ho_ImageRGB, HObject *ho_ImageInterleaved)
{
// Local iconic variables
HObject ho_ImageAffineTrans, ho_ImageRed, ho_ImageGreen;
HObject ho_ImageBlue, ho_RegionGrid, ho_RegionMoved, ho_RegionClipped;
// Local control variables
HTuple hv_PointerRed, hv_PointerGreen, hv_PointerBlue;
HTuple hv_Type, hv_Width, hv_Height, hv_HomMat2DIdentity;
HTuple hv_HomMat2DScale;
GetImagePointer3(ho_ImageRGB, &hv_PointerRed, &hv_PointerGreen, &hv_PointerBlue,
&hv_Type, &hv_Width, &hv_Height);
GenImageConst(&(*ho_ImageInterleaved), "byte", hv_Width*3, hv_Height);
//
HomMat2dIdentity(&hv_HomMat2DIdentity);
HomMat2dScale(hv_HomMat2DIdentity, 1, 3, 0, 0, &hv_HomMat2DScale);
AffineTransImageSize(ho_ImageRGB, &ho_ImageAffineTrans, hv_HomMat2DScale, "constant",
hv_Width*3, hv_Height);
//
Decompose3(ho_ImageAffineTrans, &ho_ImageRed, &ho_ImageGreen, &ho_ImageBlue);
GenGridRegion(&ho_RegionGrid, 2*hv_Height, 3, "lines", hv_Width*3, hv_Height+1);
MoveRegion(ho_RegionGrid, &ho_RegionMoved, -1, 0);
ClipRegion(ho_RegionMoved, &ho_RegionClipped, 0, 0, hv_Height-1, (3*hv_Width)-1);
ReduceDomain(ho_ImageRed, ho_RegionClipped, &ho_ImageRed);
MoveRegion(ho_RegionGrid, &ho_RegionMoved, -1, 1);
ClipRegion(ho_RegionMoved, &ho_RegionClipped, 0, 0, hv_Height-1, (3*hv_Width)-1);
ReduceDomain(ho_ImageGreen, ho_RegionClipped, &ho_ImageGreen);
MoveRegion(ho_RegionGrid, &ho_RegionMoved, -1, 2);
ClipRegion(ho_RegionMoved, &ho_RegionClipped, 0, 0, hv_Height-1, (3*hv_Width)-1);
ReduceDomain(ho_ImageBlue, ho_RegionClipped, &ho_ImageBlue);
OverpaintGray((*ho_ImageInterleaved), ho_ImageRed);
OverpaintGray((*ho_ImageInterleaved), ho_ImageGreen);
OverpaintGray((*ho_ImageInterleaved), ho_ImageBlue);
return;
}