源代码来自Halcon/Example/C#/MatchingWPF,主要目的是熟悉Halcon中Matching的应用,当然,我不会忘记WPF的介绍。
代码的整个流程可以解释为:定义模板 -> 加载不同的图像 -> 再次定位模板的位置。
针对Halcon 13版本,选择的.Net版本还是3.5.
文中注释特别提到:由于WPF中与界面的互相交流的限制,我们从控件中得到Halcon窗体最好的地方是窗体控件的HInitWindow事件中;同时注意到,在这部分可能存在异常,而且,异常没有处理的话会一直向上传播,所以,这部分最好也加上Try-Catch语句。
1)grid分布
整体上分为两行一列:
<Grid Margin="10, 10, 10, 10"> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition MaxHeight="100"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition /> </Grid.ColumnDefinitions> </Grid>
上面一行放图像,下面一行是信息显示;
2)针对上面一行Grid(0,0),又细分为一行两列:
<Grid Grid.Row="0" Grid.Column="0"> <Grid.RowDefinitions> <RowDefinition/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"> </ColumnDefinition> <ColumnDefinition MaxWidth="150"></ColumnDefinition> </Grid.ColumnDefinitions> <StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center"> <Button Name="CreateBtn" Click="CreateBtn_Click" Grid.Row="0" Grid.Column="1" MinHeight="40" Margin="10">Create Model</Button> <Button Name="StartBtn" Click="StartBtn_Click" Grid.Row="0" Grid.Column="1" MinHeight="40" Margin="10">Start</Button> <Button Name="StopBtn" Click="StopBtn_Click" Grid.Row="0" Grid.Column="1" MinHeight="40" Margin="10">Stop</Button> </StackPanel> <my:HSmartWindowControlWPF Loaded="hWindowControlWPF1_HInitWindow" Name="hWindowControlWPF1" xmlns:my="clr-namespace:HalconDotNet;assembly=halcondotnet" Grid.Column="0" Grid.Row="0" /> </Grid>
上面看到列宽分别为*和150(按钮的宽度);三个按钮又放在StackPanel(0,1)上,并分别提供Click事件;在Grid(0,0)处放置Halcon Window,在这里提供Load事件(*_HInitWindow),并补充xmlns(在这里写,感觉很奇怪!)。
3)针对下面一行Grid(1,0),细分为两行四列:
<Grid Grid.Row="1" Grid.Column="0"> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <Label Name="MatchingLabel" FontSize="12" Grid.Row="0" Grid.Column="0" HorizontalAlignment="Left">Matching:</Label> <Label Name="MatchingTimeLabel" FontSize="12" Grid.Row="0" Grid.Column="1" HorizontalAlignment="Left">Time:</Label> <Label Name="MatchingScoreLabel" FontSize="12" Grid.Row="0" Grid.Column="2" HorizontalAlignment="Left">Score:</Label> <Label Name="MeasureLabel" FontSize="12" Grid.Row="1" Grid.Column="0" HorizontalAlignment="Left">Measure:</Label> <Label Name="MeasureTimeLabel" FontSize="12" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left">Time:</Label> <Label Name="MeasureNumLabel" FontSize="12" Grid.Row="1" Grid.Column="2" HorizontalAlignment="Left">Number of leads:</Label> <Label Name="MeasureDistLabel" FontSize="12" Grid.Row="1" Grid.Column="3">Minimum lead distance:</Label> </Grid>
4)至此,界面部分完成:
5)接下来进入C#代码中,先看看Halcon窗体的HInitWindow事件:
Window = hWindowControlWPF1.HalconWindow; try { // Initialize enabled states CreateBtn.IsEnabled= true; StartBtn.IsEnabled = false; StopBtn.IsEnabled = false; // Create a timer for execution loop; Timer = new DispatcherTimer(); Timer.Interval = new TimeSpan(10); Timer.Tick += new EventHandler(Timer_Tick); // Prepare image processing string ImgType; Framegrabber = new HFramegrabber("File", 1, 1, 0, 0, 0, 0, "default", -1, "default", -1, "default", "board/board.seq", "default", 1, -1); Img = Framegrabber.GrabImage(); Img.GetImagePointer1(out ImgType, out ImgWidth, out ImgHeight); Window.SetPart(0, 0, ImgHeight - 1, ImgWidth - 1); Img.DispObj(Window); Window.SetDraw("margin"); Window.SetLineWidth(3); Rectangle = new HRegion(188.0, 182, 298, 412); Rectangle.AreaCenter(out Row, out Column); Rect1Row = Row - 102; Rect1Col = Column + 5; Rect2Row = Row + 107; Rect2Col = Column + 5; RectPhi = 0; RectLength1 = 170; RectLength2 = 5; } catch (Exception ex) { // Catch all MessageBox.Show("Error in HInitWindow:" + ex.ToString()); }
这部分完成了:指定halcon窗体;创建程序处理主体的Timer及事件Timer_Tick;加载系列图像并显示第一张;指定Rectangle;
6)创建模板的Click事件: CreateBtn_Click
HImage ImgReduced; HRegion Rectangle1 = new HRegion(); HRegion Rectangle2 = new HRegion(); CreateBtn.IsEnabled = false; Window.SetColor("red"); Window.SetDraw("margin"); Window.SetLineWidth(3); ImgReduced = Img.ReduceDomain(Rectangle); ImgReduced.InspectShapeModel(out ModelRegion, 1, 30); Rectangle1.GenRectangle2(Rect1Row, Rect1Col, RectPhi, RectLength1, RectLength2); Rectangle2.GenRectangle2(Rect2Row, Rect2Col, RectPhi, RectLength1, RectLength2); ShapeModel = new HShapeModel(ImgReduced, 4, 0, new HTuple(360.0).TupleRad().D, new HTuple(1.0).TupleRad().D, "none", "use_polarity", 30, 10); Window.SetColor("green"); Window.SetDraw("fill"); ModelRegion.DispObj(Window); Window.SetColor("blue"); Window.SetDraw("margin"); Rectangle1.DispObj(Window); Rectangle2.DispObj(Window); StopBtn.IsEnabled = false; StartBtn.IsEnabled = true;
在这之中,指定显示窗体的颜色(不是很明白?);根据上一步创建的Rectangle来构建ShapeModel;显示Model及两个IC针脚的矩形框;
7)Start按钮中指定Timer.Start();对应的Stop按钮中指定Timer.Stop()。接下来的主体就是Timer中的Tick事件;
7.1) 重新抓取图像:
Img.Dispose(); Img.GrabImage(Framegrabber); Img.DispObj(Window);
7.2)查找ShapeModel(最低匹配度70%,数量一个):
// Find the IC in the current image. S1 = HSystem.CountSeconds(); ShapeModel.FindShapeModel(Img, 0, new HTuple(360).TupleRad().D, 0.7, 1, 0.5, "least_squares", 4, 0.9, out RowCheck, out ColumnCheck, out AngleCheck, out Score); S2 = HSystem.CountSeconds(); MatchingTimeLabel.Content = "Time: " + String.Format("{0,4:F1}", (S2 - S1)*1000) + "ms"; MatchingScoreLabel.Content = "Score: ";
7.3)如果有结果存在(RowCheck.Length==1),则显示找到的Model:
MatchingScoreLabel.Content = "Score: " + String.Format("{0:F5}", Score.D); // Rotate the model for visualization purposes. Matrix.VectorAngleToRigid(new HTuple(Row), new HTuple(Column), new HTuple(0.0), RowCheck, ColumnCheck, AngleCheck); ModelRegionTrans = ModelRegion.AffineTransRegion(Matrix, "false"); Window.SetColor("green"); Window.SetDraw("fill"); ModelRegionTrans.DispObj(Window);
说实话,为什么要使用VectorAngleRigid来显示Model不是很明白。
7.4)根据上一步中的Matrix来计算两个矩形框并显示(用蓝色显示):
// Compute the parameters of the measurement rectangles. Matrix.AffineTransPixel(Rect1Row, Rect1Col, out Rect1RowCheck, out Rect1ColCheck); Matrix.AffineTransPixel(Rect2Row, Rect2Col, out Rect2RowCheck, out Rect2ColCheck); // For visualization purposes, generate the two rectangles as regions and display them. Rectangle1.GenRectangle2(Rect1RowCheck.D, Rect1ColCheck.D, RectPhi + AngleCheck.D, RectLength1, RectLength2); Rectangle2.GenRectangle2(Rect2RowCheck.D, Rect2ColCheck.D, RectPhi + AngleCheck.D, RectLength1, RectLength2); Window.SetColor("blue"); Window.SetDraw("margin"); Rectangle1.DispObj(Window); Rectangle2.DispObj(Window);
7.5)计算两个矩形框实际的位置:
S1 = HSystem.CountSeconds(); Measure1 = new HMeasure(Rect1RowCheck.D, Rect1ColCheck.D, RectPhi + AngleCheck.D, RectLength1, RectLength2, ImgWidth, ImgHeight, "bilinear"); Measure2 = new HMeasure(Rect2RowCheck.D, Rect2ColCheck.D, RectPhi + AngleCheck.D, RectLength1, RectLength2, ImgWidth, ImgHeight, "bilinear"); Measure1.MeasurePairs(Img, 2, 90, "positive", "all", out RowEdgeFirst1, out ColumnEdgeFirst1, out AmplitudeFirst1, out RowEdgeSecond1, out ColumnEdgeSecond1, out AmplitudeSecond1, out IntraDistance1, out InterDistance1); Measure2.MeasurePairs(Img, 2, 90, "positive", "all", out RowEdgeFirst2, out ColumnEdgeFirst2, out AmplitudeFirst2, out RowEdgeSecond2, out ColumnEdgeSecond2, out AmplitudeSecond2, out IntraDistance2, out InterDistance2); S2 = HSystem.CountSeconds(); MeasureTimeLabel.Content = "Time: " + String.Format("{0,5:F1}", (S2 - S1)*1000) + "ms"; Window.SetColor("red"); Window.DispLine(RowEdgeFirst1 - RectLength2*Math.Cos(AngleCheck), ColumnEdgeFirst1 - RectLength2*Math.Sin(AngleCheck), RowEdgeFirst1 + RectLength2*Math.Cos(AngleCheck), ColumnEdgeFirst1 + RectLength2*Math.Sin(AngleCheck)); Window.DispLine(RowEdgeSecond1 - RectLength2*Math.Cos(AngleCheck), ColumnEdgeSecond1 - RectLength2*Math.Sin(AngleCheck), RowEdgeSecond1 + RectLength2*Math.Cos(AngleCheck), ColumnEdgeSecond1 + RectLength2*Math.Sin(AngleCheck)); Window.DispLine(RowEdgeFirst2 - RectLength2*Math.Cos(AngleCheck), ColumnEdgeFirst2 - RectLength2*Math.Sin(AngleCheck), RowEdgeFirst2 + RectLength2*Math.Cos(AngleCheck), ColumnEdgeFirst2 + RectLength2*Math.Sin(AngleCheck)); Window.DispLine(RowEdgeSecond2 - RectLength2*Math.Cos(AngleCheck), ColumnEdgeSecond2 - RectLength2*Math.Sin(AngleCheck), RowEdgeSecond2 + RectLength2*Math.Cos(AngleCheck), ColumnEdgeSecond2 + RectLength2*Math.Sin(AngleCheck));
NumLeads = IntraDistance1.Length + IntraDistance2.Length;
MeasureNumLabel.Content = "Number of leads: " + String.Format("{0:D2}", NumLeads); MinDistance = InterDistance1.TupleConcat(InterDistance2).TupleMin(); MeasureDistLabel.Content = "Minimum lead distance: " + String.Format("{0:F3}", MinDistance.D); Window.FlushBuffer(); Measure1.Dispose(); Measure2.Dispose();
两个Measure的构造函数指定找与矩形框垂直的边;MeasureParis找边(阈值90,从灰度值低到高找),得到的IntraDistance1/2是个数组,代表14*2个边;使用DispLine显示边(用红色显示);TupleMin计算距离的最小值。
运行结果如下:
看起来运行时间很快,都是ms级的。