前言
工业视觉上位机有个老问题:算法是算法工程师写的,用的人却是现场设备工程师。两边语言不通,改一个阈值都要走一轮需求评审。项目想干的事就是把这个环节砍掉,把相机、流程、匹配这些配置全摊到界面上,谁来都能改。
项目介绍
一套 .NET 10 + WPF 写的无代码视觉软件,内核是自研的,NCC 模板匹配用 SIMD 和 FFT 加速,不依赖 OpenCV、Halcon 这类商业库。它面向工业自动化检测与定位场景,目标是让设备工程师在可视化界面里完成相机标定、模板训练、流程搭建和通讯对接,把项目从调试一路推到量产。同系列的无代码运动控制软件可以和它组合,凑成"视觉 + 运动"的一体化方案。
项目功能
|
|
|
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
流程页是核心,模板匹配负责找工件基准,输出的分数直接当合格判据用;单步调试时每步耗时摆在面板上,哪一步拖节拍一眼能看出来。
工程师页和操作员页的分离是我比较认可的设计——产线上的人不该看到一堆参数框。相机、通讯、变量这几个模块做得中规中矩,没什么好展开的。
项目特点
-
无代码编排:流程用拖拽加填参数搭,现场改逻辑不用等研发排期,设备工程师自己就能动手。
-
自研匹配内核:NCC 走 SIMD + FFT,不依赖商业视觉库,性能和可控性都在自己手里,二次定制不用看别人脸色。
-
工程师与操作员分离:两个页面、两套权限,调试项在产线上彻底藏掉,少一类误操作。
-
多流程方案并存:主流程、定位流程、检测流程各建各的,一套软件管多工位不打架。
-
耗时可见:单步调试时每步耗时直接上屏,节拍瓶颈不用翻日志对时间戳。
-
配置自动落盘:改动即保存,状态栏常驻提示,调试中途断电也不至于从零再来。
项目技术
|
|
|
|---|---|
|
|
|
|
|
DockPanel 套 |
|
|
CanvasBrush、SurfaceBrush、AccentBrush 等),改主题只动一处
|
|
|
Tag 传索引、共用 Nav_Click 处理,新增分区只需补按钮和一条分支
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
项目代码
主窗口是整个软件的外壳,导航、内容承载、状态提示都在这一层。
<Window x:Class="NoCodeVision.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="NoCodeVision · 无代码视觉软件 · 纯opencv实现 · 不使用商业视觉库 "
Width="1200" Height="780"
WindowState="Maximized"
FontFamily="{StaticResource AppleFont}"
Background="{StaticResource CanvasBrush}"
WindowStartupLocation="CenterScreen">
<DockPanel>
<!-- 顶部导航 -->
<Border DockPanel.Dock="Top" Height="74"
Background="{StaticResource SurfaceBrush}"
BorderBrush="{StaticResource LineBrush}" BorderThickness="0,0,0,1">
<Grid>
<StackPanel Orientation="Horizontal" Margin="20,0,0,0" VerticalAlignment="Center">
<Border Width="34" Height="34" CornerRadius="9" Background="{StaticResource AccentBrush}">
<TextBlock Text="N" Foreground="White" FontWeight="Bold" FontSize="19"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
<TextBlock Text="NoCodeVision" FontSize="17" FontWeight="SemiBold"
Foreground="{StaticResource TextPrimaryBrush}" Margin="12,0,0,0" />
</StackPanel>
<Border HorizontalAlignment="Center" VerticalAlignment="Center"
Background="{StaticResource SegTrackBrush}" CornerRadius="11" Padding="4">
<StackPanel Orientation="Horizontal" x:Name="SegPanel">
<Button x:Name="Nav0" Tag="0" Style="{StaticResource SegButton}" Click="Nav_Click">
<StackPanel Orientation="Horizontal">
<Image Source="{StaticResource IcoProject}" Width="24" Height="24" Margin="0,0,8,0" VerticalAlignment="Center" />
<TextBlock Text="项目" FontSize="15" VerticalAlignment="Center" />
</StackPanel>
</Button>
<!-- Nav1~Nav8:相机、运控、通讯、变量、流程、工程师、操作员、说明书 -->
</StackPanel>
</Border>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,0,20,0" VerticalAlignment="Center">
<Border Style="{StaticResource AppleStatusPill}" Background="{StaticResource AccentSoftBrush}" Padding="12,7">
<StackPanel Orientation="Horizontal">
<Ellipse Width="9" Height="9" Fill="{StaticResource SuccessBrush}" Margin="0,0,7,0" VerticalAlignment="Center" />
<TextBlock Text="就绪" FontSize="13" Foreground="{StaticResource AccentBrush}" />
</StackPanel>
</Border>
</StackPanel>
</Grid>
</Border>
<!-- 底部状态栏 -->
<Border DockPanel.Dock="Bottom" Height="30" Background="{StaticResource SurfaceBrush}"
BorderBrush="{StaticResource LineBrush}" BorderThickness="0,1,0,0">
<Grid Margin="16,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<Ellipse Width="8" Height="8" Fill="{StaticResource SuccessBrush}" Margin="0,0,7,0" />
<TextBlock x:Name="SaveStatusText" Text="配置自动保存中" FontSize="12"
Foreground="{StaticResource TextMutedBrush}" VerticalAlignment="Center" />
</StackPanel>
<TextBlock Grid.Column="2" x:Name="PageNameText" Text="项目" FontSize="12"
Foreground="{StaticResource TextMutedBrush}" VerticalAlignment="Center" />
</Grid>
</Border>
<ContentControl x:Name="ContentHost" Margin="16" />
</DockPanel>
</Window>
有两个地方值得说
第一个是 ContentHost 必须放在最后。DockPanel 的规则是先停靠先占边,顶部导航和底部状态栏都写了 DockPanel.Dock,剩下没写停靠的子元素自动填满中间。如果把 ContentHost 挪到前面去,内容区会先抢走整块空间,导航和状态栏就被挤没了。
第二个是导航按钮。九个按钮长得几乎一样,唯一区别是文字、图标和 Tag。所以没用九个 Click 处理,而是统一走 Nav_Click,Tag 里存索引,事件里转成 int 再切页面。加一个分区就是复制一个 Button、改 Tag 和文字,再在切换逻辑里补个分支,五分钟的事。
项目效果
窗口启动直接最大化,导航条固定在顶部,点哪个分区 ContentHost 里的内容跟着换,底部 PageNameText 同步显示当前页名。

左侧的自动保存提示一直在,配置改了就往磁盘写,那颗绿点配合右侧的"就绪"胶囊,运行状态算是比较直观。

内容区四周留 16 像素内缩,不贴边。整体观感比常见的灰底工业软件干净,长时间盯屏调试眼睛不会太累。
总结
项目解决的不是算法问题,是交付问题。算法做得再准,现场改不了等于零。这套东西把配置权下放给设备工程师,自研内核保证性能不拖后腿,权限分离让产线上的人碰不到调试项。配合视觉定位到运动执行这条链路是通的。中小型自动化项目拿来用,比从头攒一套要省事。

