基于HALCON的喷码字符自训练与识别

前言

最近视觉项目需求,建立10个及以上的字符模板库,然后进行生产时,提取出喷码区域与模板进行比对得到对应的分数,以分数的高低来判断喷码的好坏。
如图:喷码样品
基于HALCON的喷码字符自训练与识别_第1张图片
根据需求,可以借鉴Halcon中训练字符的思路来解决此问题。
主要流程:抠图–>训练–>识别

第一步——训练文件建立

首先用户输入检测喷码的字符,在对应位置会自动生成以喷码为名字的文件夹,然后会对应生成单个字符的子文件夹(主要用于存放单个字符图片)。
算法逻辑:

基于HALCON的喷码字符自训练与识别_第2张图片
这里我们来看看具体的Halcon源码(仅供参考)

Code:=['6','2']  //输入需要识别的字符
filenum:=|Code|  //计算字符个数
string:=['']  //初始化字符串用于将客户输入字符连接起来作为文件名
Color:=1    //0表示蓝色1表示红色

gen_rectangle1 (Rectangle, 2817.87,562.239,7395.57, 4431.49)  //绘制的喷码区域ROI,可自己绘制修改
*开始抠图
FilePath:='E:/OCR-test/10模板喷码测试/9815B'   //图像路径
list_files (FilePath, ['files','follow_links'], ImageFiles)
tuple_regexp_select (ImageFiles, ['\\.(tif|tiff|gif|bmp|jpg|jpeg|jp2|png|pcx|pgm|ppm|pbm|xwd|ima|hobj)$','ignore_case'], ImageFiles)
parse_filename (ImageFiles, BaseName, Extension, Directory)
*根据客户输入字符自动生成文件夹名字
for x := 0 to filenum-1 by 1
   string[0]:=string[0]+Code[x]   //将客户输入字符连接起来作为文件名字
endfor
Path:='E:/OCR-test/'+string+'/'    //需要创建的字符文件夹路径

*判断是否有主文件夹,没有则创建主文件夹
file_exists ('E:/OCR-test/'+string, FileExists)   //判断是否存在文件夹,存在FileExists返回1否则返回0
if(FileExists)
else
    make_dir ('E:/OCR-test/'+string)        //在自定路径创建文件夹
endif

*判断是否有字符子文件夹没有就创建子文件夹
for j := 0 to filenum-1 by 1
    file_exists (Path+Code[j], FileNum)
    if (FileNum)
    else
        make_dir (Path+Code[j])
    endif
endfor
stop ()

这里是HALCON内部的实现,可以在手动输入字符自动生成文件夹。

第二步——产品转正

产品的图片的校正,利用仿射变化来将图像进行平移旋转:
通常我们Halcon中计算出的是弧度,这里我们需要了解符号代表的意义**(顺时针方向旋转的是负号,逆时针方向旋转的是正号)**
效果如下:
转正前:
基于HALCON的喷码字符自训练与识别_第3张图片
转正后:
基于HALCON的喷码字符自训练与识别_第4张图片
具体的代码实现:

 	read_image (Image, ImageFiles[Index])
    rgb1_to_gray (Image, GrayImage)
    threshold (GrayImage, Region, 10, 170)
    connection (Region, RegionTemp)
    select_shape_std (RegionTemp, RegionTemp, 'max_area', 70)
 	orientation_region (RegionTemp, Phi)  //计算区域角度
    area_center (RegionTemp, Area, Row, Column)  //计算区域中心坐标与面积
    /*保证产品仅有竖直与横直两种情况*/
    if(Phi<0)
        AimPhi:=rad(-90)
    else
        AimPhi:=rad(90)
    endif
    vector_angle_to_rigid (Row, Column, Phi, Row, Column, AimPhi, HomMat2D) //创建变化矩阵
    affine_trans_image (Image, ImageAffineTrans, HomMat2D, 'constant', 'false')  //进行仿射变化

vector_angle_to_rigid (Row, Column, Phi, Row1, Column1, AimPhi, HomMat2D)
**Row:**变化前原点列坐标(这里是产品的中点)
**Column:**变化前原点行坐标(这里是产品的中点)
**Phi:**变化前的原点角度
**Row1:**变化后原点列坐标
**Column1:**变化后原点行坐标
**AimPhi:**变化的原点角度
**HomMat2D:**生成的旋转+平移矩阵

第三步——字符抠图

现在需要将我们的喷码字符利用图像分割技术将其提取出来
首先将图像通道分割下(这里是彩色图片,黑白图片可以直接进行提取)
此处以蓝色喷码为例:
使用通道相减突出蓝色区域,然后做简单的阈值分割

   sub_image (R, G, ImageTemp, 1, 0)   //彩色图片由于是红色喷码进行通道相减增加对比度
   threshold (ImageTemp, RegionRoi, 14, 255)  //初步选出感兴趣区域
   opening_rectangle1 (RegionRoi, RegionOpen, 5, 5)  //降低干扰
   reduce_domain (ImageTemp, RegionOpen, ImageReduced)  //图像裁剪留下喷码区域
   crop_domain (ImageReduced, ImagePart1)     //图像填充在窗口中显示

如果有的喷码喷印倾斜,需要校正。(原因:因为需要利用形态学将喷码连接为一个图像对象,防止喷码粘连)

   *矫正倾斜喷码
   text_line_slant (ImagePart1, ImagePart1,150,-rad(45),rad(45), SlantAngle)   //自动计算图像斜率         
   hom_mat2d_identity (HomMat2DIdentity)  //建立单位矩阵
   hom_mat2d_slant (HomMat2DIdentity, -SlantAngle, 'x', 0, 0, HomMat2DSlant)  //计算倾斜矫正矩阵
   affine_trans_image (ImagePart1, AffImagePart1, HomMat2DSlant, 'nearest_neighbor', 'true')  //使用倾斜矫正矩阵将倾斜的喷码变化为竖直  

倾斜喷码:
基于HALCON的喷码字符自训练与识别_第5张图片
倾斜校正:
基于HALCON的喷码字符自训练与识别_第6张图片
接着,将需要的喷码提取出来进行***归一化操作***(这步很重要!!!)就是将抠取出的喷码图片为一张二值图像(只有黑和白)。

	 threshold (AffImagePart1, RegionRoi, 14, 255)
     opening_rectangle1 (RegionRoi, RegionOpen, 10, 5)     
     dilation_rectangle1 (RegionOpen, RegionTemp, 1, 200)  //竖直膨胀
     dilation_rectangle1 (RegionTemp, RegionTemp, 10, 1)   //横向膨胀
     connection (RegionTemp, RegionTemp)
     intersection (RegionTemp, RegionOpen, RegionInter)
     select_shape (RegionInter, RegionTemp, ['height','area','width'], \
                      'and', [250,3000,90], [500,50000,300])
    count_obj (RegionTemp, Number)
    sort_region (RegionTemp, RegionTemp, 'character', 'true', 'row')  //字符排序
   /*以下代码表示将单个喷码单独截取保存进入对应的子文件夹*/
   for RedIndex := 1 to Number by 1  
   select_obj (RegionTemp, ObjectRegionTemp, RedIndex)     //将排好顺序的喷码按照序号单个处理
   smallest_rectangle2 (ObjectRegionTemp, Row4, Column4, Phi2, Length11, Length21)
   gen_rectangle2 (Rectangle3, Row4, Column4, Phi2, Length11, Length21)
   reduce_domain (AffImagePart1, Rectangle3, ImageReduced1)
   crop_domain (ImageReduced1, ImagePart)
   binary_threshold (ImagePart, RegionCode, 'max_separability', 'light', UsedThreshold1)   //单个字符提取
   opening_rectangle1 (RegionCode, RegionCode, 10, 5)
   get_image_size (ImagePart, Width, Height)  //计算窗口图片大小
   gen_image_const (ImageBk, 'byte', Width, Height)   //创建一个与窗口大小一致的图片
   overpaint_region (ImageBk, ImageBk, 255, 'fill')   //将创建的图片背景喷印为白色
    overpaint_region (ImageBk, RegionCode, 0, 'fill')  //将喷码制定为黑色喷印在背景上
   tuple_rand (1, Rand)     //生成一个随机数
   Index_rand:=int(Rand*1000)   
   write_image (ImageBk, 'jpeg', 0, Path+Code[RedIndex-1]+'/'+Code[RedIndex-1]+'_'+Index_rand)  //保存归一化后的字符模板

第四步——字符训练

字符的图片现在有了,接下来就可以通过简单的二值阈值分割得到字符区域:
字符抠图结果:
基于HALCON的喷码字符自训练与识别_第7张图片
算法逻辑:
基于HALCON的喷码字符自训练与识别_第8张图片
首先我们需要建立一个“字符集文件”字符集文件的格式为“.trf”
创建一个Halcon中定义的一个错误处理来规避重复创建字符集文件,默认下一次的创建会复写字符集。

TrainFile1:=string+'-0-9A-Z.trf'
dev_set_check ('~give_error')  //错误处理起始
delete_file (TrainFile1)       //删除旧版本字符集
dev_set_check ('give_error')   

将字符写入字符集:
此处两个for循坏,是实现自动将字符写入对应子文件

for FileIndex := 0 to filenum-1 by 1
   list_files (Path+Code[FileIndex], ['files','follow_links'], ImageFiles)
   tuple_regexp_select (ImageFiles, ['\\.(tif|tiff|gif|bmp|jpg|jpeg|jp2|png|pcx|pgm|ppm|pbm|xwd|ima|hobj)$','ignore_case'], ImageFiles)
     for Index := 0 to |ImageFiles| - 1 by 1
         read_image (Image, ImageFiles[Index])
         binary_threshold (Image, Region, 'max_separability', 'dark', UsedThreshold)
         opening_rectangle1 (Region, RegionOpen, 5, 5)
         append_ocr_trainf (RegionOpen, GrayImage,Code[FileIndex], TrainFile1) 
     endfor 
endfor
stop ()

字符集的创建完毕后我们开始自己训练我们的分类器(MLP)文件格式为“.omc”
其中uniq(sort(Code)),表示分类的类别不能有重复的,这个是合并重复的标签。
例如:标签中a[‘0’,‘0’,‘0’,‘2’,‘1’]使用了后会变为a[‘0’,‘1’,‘2’]

read_ocr_trainf_names (TrainFile1, CharacterNames, CharacterCount)
create_ocr_class_mlp (8, 10, 'constant', 'default', uniq(sort(Code)), 80, 'none', 10, 42, OCRHandle)
trainf_ocr_class_mlp (OCRHandle, TrainFile1, 200, 1, 0.01, Error, ErrorLog)
FontFile:='shanjin.omc'
write_ocr_class_mlp(OCRHandle,FontFile)
clear_ocr_class_mlp (OCRHandle)
stop ()

第五步—— 字符检测识别

最后进行我们字符的识别,同样的将字符提取出来,读取我们自己的分类器(MLP)进行OCR识别
算法逻辑:
基于HALCON的喷码字符自训练与识别_第9张图片
这里我们来看看具体的源码(仅供参考)

list_files (FilePath, ['files','follow_links'], ImageFiles)
tuple_regexp_select (ImageFiles, ['\\.(tif|tiff|gif|bmp|jpg|jpeg|jp2|png|pcx|pgm|ppm|pbm|xwd|ima|hobj)$','ignore_case'], ImageFiles)
for Index := 0 to |ImageFiles| - 1 by 1
    read_image (Image, ImageFiles[Index])
    rgb1_to_gray (Image, GrayImage)
    Locate (Image, ImageTemp, ProductRegion, [])  //图像定位与转正
  
    CodeDiff:=31
    Params:=[Color,CodeDiff]
    ExtracteRegionOCV (ImageTemp, Rectangle, ProductRegion, RegionResult, Params, NumberDots, 			NumberAllDots)   //喷码的提取
    stop ()
    fill_up_shape (RegionResult, RegionFillUp, 'area', 1, 100)
    sort_region (RegionFillUp, SortedRegions, 'character', 'true', 'row')
    union1 (SortedRegions, RegionUnion)
    difference (ImageTemp, RegionUnion, RegionDifference)
    paint_region (RegionDifference, GrayImage, ImageOcrRaw, 255, 'fill')
    paint_region (RegionUnion, ImageOcrRaw, ImageOcr, 0, 'fill')
    read_ocr_class_mlp (FontFile, OCRHandle)
    do_ocr_multi_class_mlp (SortedRegions, ImageOcr, OCRHandle, Class, Confidence)
    stop ()
endfor

最终结果:
基于HALCON的喷码字符自训练与识别_第10张图片

写在最后

在我们训练完毕MLP后我们想要测试他的能力怎么办呢?我们可以使用助手的功能:
在调试自己训练的分类器是否正确的时候可以打开Halcon自带的OCR助手来查看自己的分类器能力。
基于HALCON的喷码字符自训练与识别_第11张图片
在其中加载自己的字符集和分类器
基于HALCON的喷码字符自训练与识别_第12张图片
这里我们还可以点击编辑菜单中选择"生成变化"这里可以我们的字符集变得更丰富:
基于HALCON的喷码字符自训练与识别_第13张图片
然后用分类器(MLP)测试下识别的能力:
基于HALCON的喷码字符自训练与识别_第14张图片

你可能感兴趣的:(Halcon,算法)