最近视觉项目需求,建立10个及以上的字符模板库,然后进行生产时,提取出喷码区域与模板进行比对得到对应的分数,以分数的高低来判断喷码的好坏。
如图:喷码样品
根据需求,可以借鉴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中计算出的是弧度,这里我们需要了解符号代表的意义**(顺时针方向旋转的是负号,逆时针方向旋转的是正号)**
效果如下:
转正前:
转正后:
具体的代码实现:
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') //使用倾斜矫正矩阵将倾斜的喷码变化为竖直
倾斜喷码:
倾斜校正:
接着,将需要的喷码提取出来进行***归一化操作***(这步很重要!!!)就是将抠取出的喷码图片为一张二值图像(只有黑和白)。
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) //保存归一化后的字符模板
字符的图片现在有了,接下来就可以通过简单的二值阈值分割得到字符区域:
字符抠图结果:
算法逻辑:
首先我们需要建立一个“字符集文件”字符集文件的格式为“.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识别
算法逻辑:
这里我们来看看具体的源码(仅供参考)
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
在我们训练完毕MLP后我们想要测试他的能力怎么办呢?我们可以使用助手的功能:
在调试自己训练的分类器是否正确的时候可以打开Halcon自带的OCR助手来查看自己的分类器能力。
在其中加载自己的字符集和分类器
这里我们还可以点击编辑菜单中选择"生成变化"这里可以我们的字符集变得更丰富:
然后用分类器(MLP)测试下识别的能力: