Datamatrix原名Datacode,由美国国际资料公司(International Data Matrix, 简称ID Matrix)于1989年发明。
Datamatrix是一种矩阵式二维条码,其发展的构想是希望在较小的条码标签上存入更多的资料量。Datamatrix的最小尺寸是目前所有条码中最小的,尤其特别适用于小零件的标识,以及直接印刷在实体上。
本文介绍的是使用libdmtx结合OpenCV对工业产品上的Datamatrix二维码进行识别。项目中使用到的libdmtx的链接库下载见:链接库下载地址。使用时,只需将对应版本的lib、dll和dmtx.h保存在工程的Debug目录下,并在VS的附加依赖项中写入libdmtx.lib即可,步骤:项目属性——链接器——输入——附加依赖项。至于OpenCV的环境配置,网上教程很多,这里就不多说了。
测试程序如下:
#include <opencv2/opencv.hpp>
#include <iostream>
#include "dmtx.h"
using namespace cv;
using namespace std;
int main()
{
DmtxMessage *msg;
DmtxRegion *reg;
Mat dst;
double time = getTickCount();
Mat src=imread("1.bmp");
if (!src.data)
{
cout << "Load image failed!" << endl;
}
cvtColor(src, src, CV_BGR2GRAY);
DmtxImage *image;
image = dmtxImageCreate(src.data, src.cols, src.rows, DmtxPack8bppK);//注意图片类型
DmtxDecode *dec = dmtxDecodeCreate(image, 1);//解码
reg = dmtxRegionFindNext(dec, NULL); //获取二维码位置,第二个参数表示扫描时间上限,达到时间上限退出扫描
if (reg != NULL) {
msg = dmtxDecodeMatrixRegion(dec, reg, DmtxUndefined);//解码信息
if (msg != NULL)
{
cout<< msg->output<<endl;
dmtxMessageDestroy(&msg);
}
dmtxRegionDestroy(®);
}
else
{
cout<< "Get region failed!"<<endl;
}
dmtxDecodeDestroy(&dec);
dmtxImageDestroy(&image);
time = (getTickCount() - time) / getTickFrequency();
cout << "the processing time is :" << time << endl;
cin.get();
return 0;
}
测试程序虽然能够成功识别二维码并提取二维码信息,但是对于输入图片较大的情况下,识别时间较长。于是乎,我写了个能设置识别区域(即设置ROI)的MFC,具体设置如下:
新建一个基于对话框的MFC应用程序,在对话框中添加一个picture control控件用于显示图片,两个编辑框用于显示二维码信息和识别时间,显示二维码信息的编辑框关联CString型变量m_decode,显示识别时间的编辑框关联double型变量m_time,四个button分别为加载图片、识别二维码、设置ROI和退出。我的MFC界面如下:
除了将libdmtx的lib文件,dll文件和dmtx.h文件放在工程Debug目录下和设置附加依赖项之外,还要在工程的xxxDlg.h文件中包含dmtx.h头文件。
接下来,在xxxDlg.cpp文件的OnInitDialog函数中添加以下代码,目的是将图片显示在picture control中。
namedWindow("view", WINDOW_AUTOSIZE);
HWND hWnd = (HWND)cvGetWindowHandle("view");
HWND hParent = ::GetParent(hWnd);
::SetParent(hWnd, GetDlgItem(IDC_STATIC)->m_hWnd);
::ShowWindow(hParent, SW_HIDE);
双击加载图片的button,添加以下代码:
CString szFileters=_T("Bmp Files(*.bmp)|*.bmp||"); //指定文件过滤器中的文件名称
CFileDialog fileDlg(TRUE,L"bmp",L"bmp.bmp",OFN_FILEMUSTEXIST|OFN_HIDEREADONLY,szFileters,NULL);
//设置文件对话框的性质
if (fileDlg.DoModal()==IDOK)
{
m_decode=_T("");
m_time=0;
UpdateData(FALSE);
USES_CONVERSION;
CString filename=fileDlg.GetPathName();//获取文件路径名称
string strtemp = W2A(filename.GetBuffer(0));
Mat srcImage = imread(strtemp);
CRect rect;
GetDlgItem(IDC_STATIC)->GetClientRect(&rect);
//Rect dst(rect.left, rect.top, rect.right, rect.bottom);
resize(srcImage, dstImage, Size(rect.Width(), rect.Height()));
dstImage.copyTo(dst);
imshow("view", dst);
}
将图片保存在工程目录下更方便加载,加载图片的功能完成。读取图片时,使用了CString到String的转换:
// Unicode下Cstring到String的转换:
USES_CONVERSION;
CString filename=fileDlg.GetPathName();//获取文件路径名称
string strtemp = W2A(filename.GetBuffer(0));
Mat srcImage = imread(strtemp);
接下来是识别二维码,双击识别二维码的button,添加以下代码:
DmtxMessage *msg;
DmtxRegion *reg;
double time=getTickCount();
//resize(src,src,Size(300,400));
Mat src;
if(!dst.data)
{
m_decode="Load image failed!";
UpdateData(FALSE);
}
dst.copyTo(src);
cvtColor(src,src,CV_BGR2GRAY);
DmtxImage *image;
image = dmtxImageCreate(src.data, src.cols, src.rows, DmtxPack8bppK);//注意图片类型
DmtxDecode *dec = dmtxDecodeCreate(image, 1);//解码
reg = dmtxRegionFindNext(dec, NULL); //获取二维码位置,第二个参数表示扫描时间上限,达到时间上限退出扫描
if (reg != NULL) {
msg = dmtxDecodeMatrixRegion(dec, reg, DmtxUndefined);//解码信息
if (msg != NULL)
{
m_decode=msg->output;
UpdateData(FALSE);
dmtxMessageDestroy(&msg);
}
dmtxRegionDestroy(®);
}
else
{
m_decode="Get region failed!";
UpdateData(FALSE);
}
dmtxDecodeDestroy(&dec);
dmtxImageDestroy(&image);
time=(getTickCount()-time)/getTickFrequency();
m_time=time;
UpdateData(FALSE);
接下来设置识别区域的功能,双击设置区域的button,添加以下代码:
dstImage.copyTo(img); //图片接口
dstImage.copyTo(tmp); //临时变量
setMouseCallback("view",on_mouse,0);//调用回调函数
m_decode=_T("");
m_time=0;
UpdateData(FALSE);
在设置ROI功能中,用到了on_mouse函数,需在程序中实现此函数,具体代码如下:
void on_mouse(int event,int x,int y,int flags,void *ustc)//event鼠标事件代号,x,y鼠标坐标,flags拖拽和键盘操作的代号
{
static Point pre_pt = (-1,-1);//初始坐标
static Point cur_pt = (-1,-1);//实时坐标
char temp[16];
if (event == CV_EVENT_LBUTTONDOWN)//左键按下,读取初始坐标,并在图像上该点处划圆
{
dstImage.copyTo(img);//将原始图片复制到img中
sprintf_s(temp,"(%d,%d)",x,y);
pre_pt = Point(x,y);
putText(img,temp,pre_pt,FONT_HERSHEY_SIMPLEX,0.5,Scalar(0,255,0),1,8);//在窗口上显示坐标
circle(img,pre_pt,2,Scalar(255,0,0),CV_FILLED,CV_AA,0);//划圆
imshow("view",img);
}
else if (event == CV_EVENT_MOUSEMOVE && !(flags & CV_EVENT_FLAG_LBUTTON))//左键没有按下的情况下鼠标移动的处理函数
{
img.copyTo(tmp);//将img复制到临时图像tmp上,用于显示实时坐标
sprintf_s(temp,"(%d,%d)",x,y);
cur_pt = Point(x,y);
putText(tmp,temp,cur_pt,FONT_HERSHEY_SIMPLEX,0.5,Scalar(0,255,0));//只是实时显示鼠标移动的坐标
imshow("view",tmp);
}
else if (event == CV_EVENT_MOUSEMOVE && (flags & CV_EVENT_FLAG_LBUTTON))//左键按下时,鼠标移动,则在图像上划矩形
{
img.copyTo(tmp);
sprintf_s(temp,"(%d,%d)",x,y);
cur_pt = Point(x,y);
putText(tmp,temp,cur_pt,FONT_HERSHEY_SIMPLEX,0.5,Scalar(0,255,0));
rectangle(tmp,pre_pt,cur_pt,Scalar(0,0,255,0),1,8,0);//在临时图像上实时显示鼠标拖动时形成的矩形
imshow("view",tmp);
}
else if (event == CV_EVENT_LBUTTONUP)//左键松开,将在图像上划矩形
{
dstImage.copyTo(img);
sprintf_s(temp,"(%d,%d)",x,y);
cur_pt = Point(x,y);
putText(img,temp,cur_pt,FONT_HERSHEY_SIMPLEX,0.5,Scalar(0,255,0));
circle(img,pre_pt,2,Scalar(255,0,0,0),CV_FILLED,CV_AA,0);
rectangle(img,pre_pt,cur_pt,Scalar(0,255,0,0),1,8,0);//根据初始点和结束点,将矩形画到img上
imshow("view",img);
img.copyTo(tmp);
//截取矩形包围的图像,并保存到dst中
int width = abs(pre_pt.x - cur_pt.x);
int height = abs(pre_pt.y - cur_pt.y);
//对原图进行裁剪
dst =dstImage(Rect(min(cur_pt.x,pre_pt.x),min(cur_pt.y,pre_pt.y),width,height));
waitKey(0);
}
}
至此,所有功能已经实现。编译时报错部分未定义的变量,需自行在xxxDlg.cpp中定义,另外要为on_mouse函数在xxxDlg.cpp中添加声明:
void on_mouse(int event,int x,int y,int flags,void *ustc);
写博文只为了记录分享,有什么不对的地方,欢迎指正!