本人为机器视觉入门小白,自己突发奇想,想将自己阶段性的学习总结一下,所以基于自己的条件(即家里没有工业相机、镜头和目标对象)做了一个硬币识别的小Demo。一共分为以下几个部分:
1.图片数据来源和分析
2.HALCON实现
3.C#搭建界面框架
4.C#中嵌入HALCON代码,即联合编程
5.导出.exe文件,运行以及Debug
目标是这样的,在图片中识别出每种类型的硬币数量,然后计算出总钱数。先上结果。C#界面和结果:
1.图片数据来源和分析
图片大小及格式均统一,拍摄的时候已经尽可能的去除了背景噪点,所以图像处理的要求并不很高(重点在于思路)。硬币区分原理本人代码采用直径范围来判断(也可以半径或区域面积,HALCON算子可选项很多)
2.HALCON实现
首先在HALCON自带的HDevelop软件里实现图像处理部分。HALCON代码如下。HD里面采用文件夹读图以方便测试,C#里会修改成选择文件夹图片
list_files ('F:/09 Halcon练习项目/硬币识别/硬币识别APP2/picture', ['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])
* Image Acquisition 01: Do something
*计算图像尺寸,得到图像宽度和高度
get_image_size (Image, Width, Height)
*灰度化
rgb1_to_gray (Image, GrayImage1)
*平滑处理
median_image (GrayImage1, ImageMedian, 'square', 1, 'mirrored')
*二进制阈值分割图像
threshold (ImageMedian, Region, 120, 255)
*填充图像中各个区域的小孔
fill_up (Region, RegionFillUp)
*区域连通
connection (RegionFillUp, ConnectedRegions)
*面积筛选
select_shape(ConnectedRegions, SelectedRegions, 'area', 'and', 13000, 36296)
*圆形度筛选
select_shape(SelectedRegions, SelectedRegions1, 'circularity', 'and', 0.75, 1)
*计算区域数量
count_obj (SelectedRegions1, Number)
*得到各个区域数据
gen_contour_region_xld (SelectedRegions1, Contours1, 'center')
fit_circle_contour_xld (Contours1, 'algebraic', -1, 0, 0, 3, 2, Row, Column, Radius, StartPhi, EndPhi, PointOrder)
i:=0
dev_display (Image)
*设置显示颜色
dev_set_color ('red')
*设置显示线宽
dev_set_line_width (3)
*循环,分别将所有区域画出
for J:= 0 to Number by 1
if(J
3.C#搭建界面框架
图片显示窗口采用HALCON的HWindowControl。
4.C#中嵌入HALCON代码,即联合编程
第2步中的HALCON代码导出成C#代码,即.cs文件。然后将图像算法部分作为一个函数放置在C#代码中。
对于我这种入门菜鸟来说,这里有两点需要学习和练习。一个是窗口适应图片大小,二个是C#选择文件夹里的文件
(1)窗口适应图片,论坛里借鉴别的大神的代码,加载到HALCON显示图片的那行代码的前面。
HOperatorSet.GetImageSize(ho_Image, out hv_Width, out hv_Height);
HOperatorSet.SetPart(hv_ExpDefaultWinHandle, 0, 0, hv_Height - 1, hv_Width - 1);
这里我的HWindowControl窗口长宽比和图片像素长宽比是一样的(数值可以不一样,比例要一样)这样图片不会因为适应窗口出现变形,只会同比例缩小放大。
(2)C#选择文件,即点击界面上的“加载图片”按钮,出现文件夹目录。同样借鉴的大神代码
private void button1_Click(object sender, EventArgs e)
{
OpenFileDialog openFileDialog1 = new OpenFileDialog();
openFileDialog1.Filter = "PNG文件|*.png*|JPEG文件|*.jpg*|BMP文件|*.bmp*";
openFileDialog1.RestoreDirectory = true;
openFileDialog1.FilterIndex = 1;
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{ ImagePath = openFileDialog1.FileName;
HD.ReadPicture(hWindowControl1.HalconWindow, ImagePath);
}
}
button1_Click即是“打开图片”按钮。
关于Halcon算子部分的代码,我是单独建的一个函数,然后在Form1里面调用该函数,以实现图像处理和显示。
代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using HalconDotNet;
public partial class HDevelopExport
{
public void InitHalcon()
{
// Default settings used in HDevelop
HOperatorSet.SetSystem("do_low_error", "false");
}
public HTuple hv_ExpDefaultWinHandle;
HObject ho_Image = null, ho_GrayImage1 = null;
HObject ho_ImageMedian = null, ho_Region = null, ho_RegionFillUp = null;
HObject ho_ConnectedRegions = null, ho_SelectedRegions = null;
HObject ho_SelectedRegions1 = null, ho_Contours = null, ho_Contours2 = null;
HTuple hv_ImageFiles = null, hv_Index = null;
HTuple hv_Width = new HTuple(), hv_Height = new HTuple();
HTuple hv_Number = new HTuple(), hv_Row = new HTuple();
HTuple hv_Column = new HTuple(), hv_Radius = new HTuple();
HTuple hv_StartPhi = new HTuple(), hv_EndPhi = new HTuple();
HTuple hv_PointOrder = new HTuple(), hv_i = new HTuple();
HTuple hv_J = new HTuple();
public void ReadPicture(HTuple Window, string ImagePath)
{
//读图并显示
hv_ExpDefaultWinHandle = Window;
HOperatorSet.GenEmptyObj(out ho_Image);
ho_Image.Dispose();
HOperatorSet.ReadImage(out ho_Image, ImagePath);
HOperatorSet.GetImageSize(ho_Image, out hv_Width, out hv_Height);
HOperatorSet.SetPart(hv_ExpDefaultWinHandle, 0, 0, hv_Height - 1, hv_Width - 1);
HOperatorSet.DispObj(ho_Image, hv_ExpDefaultWinHandle);
}
public void Process(HTuple Window,out int a, out int b ,out int c)
{
hv_ExpDefaultWinHandle = Window;
HOperatorSet.GenEmptyObj(out ho_GrayImage1);
HOperatorSet.GenEmptyObj(out ho_ImageMedian);
HOperatorSet.GenEmptyObj(out ho_Region);
HOperatorSet.GenEmptyObj(out ho_RegionFillUp);
HOperatorSet.GenEmptyObj(out ho_ConnectedRegions);
HOperatorSet.GenEmptyObj(out ho_SelectedRegions);
HOperatorSet.GenEmptyObj(out ho_SelectedRegions1);
HOperatorSet.GenEmptyObj(out ho_Contours);
HOperatorSet.GenEmptyObj(out ho_Contours2);
//灰度化
ho_GrayImage1.Dispose();
HOperatorSet.Rgb1ToGray(ho_Image, out ho_GrayImage1);
ho_ImageMedian.Dispose();
HOperatorSet.MedianImage(ho_GrayImage1, out ho_ImageMedian, "square", 1, "mirrored");
//二进制阈值分割图像,最大限度的可分析,提取‘黑’或‘白’,自动阈值使用的阈值值UsedThreshold
ho_Region.Dispose();
HOperatorSet.Threshold(ho_ImageMedian, out ho_Region, 120, 255);
//填充图像中各个区域的小孔,填充后区域个数不变
ho_RegionFillUp.Dispose();
HOperatorSet.FillUp(ho_Region, out ho_RegionFillUp);
//区域连通
ho_ConnectedRegions.Dispose();
HOperatorSet.Connection(ho_RegionFillUp, out ho_ConnectedRegions);
ho_SelectedRegions.Dispose();
HOperatorSet.SelectShape(ho_ConnectedRegions, out ho_SelectedRegions, "area",
"and", 13000, 36296);
ho_SelectedRegions1.Dispose();
HOperatorSet.SelectShape(ho_SelectedRegions, out ho_SelectedRegions1, "circularity",
"and", 0.75, 1);
HOperatorSet.CountObj(ho_SelectedRegions1, out hv_Number);
ho_Contours.Dispose();
HOperatorSet.GenContourRegionXld(ho_SelectedRegions1, out ho_Contours, "center");
HOperatorSet.FitCircleContourXld(ho_Contours, "algebraic", -1, 0, 0, 3, 2,
out hv_Row, out hv_Column, out hv_Radius, out hv_StartPhi, out hv_EndPhi,
out hv_PointOrder);
hv_i = 0;
HOperatorSet.GetImageSize(ho_Image, out hv_Width, out hv_Height);
HOperatorSet.SetPart(hv_ExpDefaultWinHandle, 0, 0, hv_Height - 1, hv_Width - 1);
HOperatorSet.DispObj(ho_GrayImage1, hv_ExpDefaultWinHandle);
HOperatorSet.SetColor(hv_ExpDefaultWinHandle, "red");
HOperatorSet.SetLineWidth(hv_ExpDefaultWinHandle, 2);
HTuple end_val26 = hv_Number;
HTuple step_val26 = 1;
for (hv_J = 0; hv_J.Continue(end_val26, step_val26); hv_J = hv_J.TupleAdd(step_val26))
{
if ((int)(new HTuple(hv_J.TupleLess(hv_Number))) != 0)
{
ho_Contours2.Dispose();
HOperatorSet.GenCircleContourXld(out ho_Contours2, hv_Row.TupleSelect(hv_i),
hv_Column.TupleSelect(hv_i), hv_Radius.TupleSelect(hv_i), 0, 6.28318,
"positive", 1);
hv_i = hv_i + 1;
HOperatorSet.DispObj(ho_Contours2, hv_ExpDefaultWinHandle);
}
}
HOperatorSet.DispObj(ho_Contours2, hv_ExpDefaultWinHandle);
ho_Image.Dispose();
ho_GrayImage1.Dispose();
ho_ImageMedian.Dispose();
ho_Region.Dispose();
ho_RegionFillUp.Dispose();
ho_ConnectedRegions.Dispose();
ho_SelectedRegions.Dispose();
ho_SelectedRegions1.Dispose();
ho_Contours.Dispose();
ho_Contours2.Dispose();
a = 0;
b = 0;
c = 0;
for (int i = 0; i < hv_Number; i++)
{
if (hv_Radius[i] > 91)
{
a++; //一元
}
else if (hv_Radius[i] < 74)
{
c++; //一角
}
else
{
b++; //五角
}
}
}
}
关于这个代码有以下几点:
(1)ReadPicture函数是读图并显示;
(2)Process函数即图像处理部分;
(3)一般的,一个函数只能返回一个值,这里我用的Out以返回多个值(即1元,5角和1角三种硬币);
(4)通过三种硬币的直径的范围来判断和分类。
5.导出.exe文件,运行以及Debug
我想把这个小程序做成绿色版,即不需要安装,可以U盘直接即插即用的,但是直接将VS文件夹里的bin\Debug里面的.exe文件导出会出现自己电脑可以实现,别的电脑上用不了的情况。经万能的CSDN搜索,是需要将halcon.dll等文件复制到程序文件夹中。.exe文件可以创建快捷方式和改名。
初次发自己的经历,多多指教。