大数跨境

不依赖第三方视觉库,用 .NET 10 + WPF 搭一套无代码视觉上位机

不依赖第三方视觉库,用 .NET 10 + WPF 搭一套无代码视觉上位机 dotNET跨平台
2026-10-26
7
导读:前言工业视觉上位机有个老问题:算法是算法工程师写的,用的人却是现场设备工程师。两边语言不通,改一个阈值都要走

前言

工业视觉上位机有个老问题:算法是算法工程师写的,用的人却是现场设备工程师。两边语言不通,改一个阈值都要走一轮需求评审。项目想干的事就是把这个环节砍掉,把相机、流程、匹配这些配置全摊到界面上,谁来都能改。

项目介绍

一套 .NET 10 + WPF 写的无代码视觉软件,内核是自研的,NCC 模板匹配用 SIMD 和 FFT 加速,不依赖 OpenCV、Halcon 这类商业库。它面向工业自动化检测与定位场景,目标是让设备工程师在可视化界面里完成相机标定、模板训练、流程搭建和通讯对接,把项目从调试一路推到量产。同系列的无代码运动控制软件可以和它组合,凑成"视觉 + 运动"的一体化方案。

项目功能

功能
主要能力
项目
工程文件新建、打开、保存,整体配置的落盘与恢复
相机
多路相机接入;曝光、增益、分辨率、像素格式配置;连续、软触发、硬触发三种采集模式;实时图像预览
运控
与 NoCodeMotion 联动的运动控制参数与点位管理
通讯
与 PLC 等外部设备的连接配置和数据交互
变量
全局变量的定义、引用与运行期观察
流程
多套流程方案(主流程、定位流程、检测流程)创建与切换;拖拽添加图像采集、模板匹配、几何测量、逻辑判断、结果输出等算子;配置 ROI、匹配阈值、超时重试;条件分支控制;单步调试与完整运行;各步骤耗时实时展示
工程师
视觉引擎、相机、通讯等模块运行状态总览;开发、调试、生产三种模式切换;日志等级自定义;日志导出
操作员
产线精简界面,屏蔽调试项,只保留启动、停止、结果查看
说明书
内置使用文档入口

流程页是核心,模板匹配负责找工件基准,输出的分数直接当合格判据用;单步调试时每步耗时摆在面板上,哪一步拖节拍一眼能看出来。

工程师页和操作员页的分离是我比较认可的设计——产线上的人不该看到一堆参数框。相机、通讯、变量这几个模块做得中规中矩,没什么好展开的。

项目特点

  • 无代码编排:流程用拖拽加填参数搭,现场改逻辑不用等研发排期,设备工程师自己就能动手。

  • 自研匹配内核:NCC 走 SIMD + FFT,不依赖商业视觉库,性能和可控性都在自己手里,二次定制不用看别人脸色。

  • 工程师与操作员分离:两个页面、两套权限,调试项在产线上彻底藏掉,少一类误操作。

  • 多流程方案并存:主流程、定位流程、检测流程各建各的,一套软件管多工位不打架。

  • 耗时可见:单步调试时每步耗时直接上屏,节拍瓶颈不用翻日志对时间戳。

  • 配置自动落盘:改动即保存,状态栏常驻提示,调试中途断电也不至于从零再来。

项目技术

层级
技术选型与做法
前端框架
.NET 10 + WPF,窗口最大化启动,1200×780 为还原尺寸
界面布局
DockPanel 套 Grid,顶部导航与底部状态栏先停靠,ContentHost 最后填充剩余空间
样式管理
资源字典统一管理配色与控件样式(CanvasBrush、SurfaceBrush、AccentBrush 等),改主题只动一处
导航实现
九个按钮统一 Tag 传索引、共用 Nav_Click 处理,新增分区只需补按钮和一条分支
算法内核
C++ 编写,通过互操作供 WPF 前端调用
模板匹配
NCC 算法,SIMD 向量化 + FFT 频域卷积,替代逐像素滑窗
相机层
支持全局快门与卷帘两种传感器,采集模式覆盖连续、软触发、硬触发
通讯层
独立模块承接 PLC 通讯,流程输出结果可直接下发外部设备
配置管理
工程配置自动保存,底部状态栏常驻保存状态提示

项目代码

主窗口是整个软件的外壳,导航、内容承载、状态提示都在这一层。

<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 像素内缩,不贴边。整体观感比常见的灰底工业软件干净,长时间盯屏调试眼睛不会太累。

流程
工程师
运控
通讯
操作员
说明书
变量


总结

项目解决的不是算法问题,是交付问题。算法做得再准,现场改不了等于零。这套东西把配置权下放给设备工程师,自研内核保证性能不拖后腿,权限分离让产线上的人碰不到调试项。配合视觉定位到运动执行这条链路是通的。中小型自动化项目拿来用,比从头攒一套要省事。

【声明】内容源于网络
0
0
dotNET跨平台
专注于.NET Core的技术传播。在这里你可以谈微软.NET,Mono的跨平台开发技术。在这里可以让你的.NET项目有新的思路,不局限于微软的技术栈,横跨Windows,
内容 2380
粉丝 0
dotNET跨平台 专注于.NET Core的技术传播。在这里你可以谈微软.NET,Mono的跨平台开发技术。在这里可以让你的.NET项目有新的思路,不局限于微软的技术栈,横跨Windows,
总阅读87.3k
粉丝0
内容2.4k