【零基础入门】Python机器视觉第六阶段:综合项目实战——构建工业缺陷检测原型系统

经过前五个阶段的学习,我们已经掌握了Python基础、OpenCV图像处理、传统算法、深度学习(PyTorch)以及YOLOv8目标检测。现在,是时候将这些知识整合起来,构建一个接近真实工业场景的缺陷检测原型系统了。

本阶段我们将以C# WPF作为上位机界面,Python YOLOv8作为检测服务,并模拟工业相机和运动控制,实现一个完整的“运动→采图→检测→分选”闭环系统。即使没有真实硬件,我们也能通过模拟方式完成全部开发,最终产出一个可以写入简历的项目。

本文所有代码均可直接复制运行,注释非常详细。建议按照步骤逐步实现。


一、本阶段学习目标

  • 掌握C#与Python的混合编程(进程调用方式)
  • 学会模拟工业相机(MockCameraService
  • 学会模拟运动控制卡(MotionController
  • 掌握WPF MVVM模式的基本使用
  • 能够将YOLOv8检测服务集成到C#应用中
  • 构建一个完整的演示系统,实现多工位检测流程
  • 学会项目结构组织和简历撰写技巧

二、系统整体架构

2.1 架构图

┌─────────────────────────────────────────────────────────────┐
│                         C# 上位机 (WPF)                      │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐  │
│  │ 主界面       │  │ 运动控制服务 │  │ 相机服务           │  │
│  │ (MVVM)      │◄─┤ (Motion)    │  │ (MockCamera)       │  │
│  └──────┬──────┘  └──────┬──────┘  └──────────┬──────────┘  │
│         │                 │                    │               │
│         └─────────────────┼────────────────────┘               │
│                           │                                    │
│                    ┌──────▼──────┐                            │
│                    │检测服务调用  │                            │
│                    │(Python进程) │                            │
│                    └──────┬──────┘                            │
└───────────────────────────┼────────────────────────────────────┘
                            │
                    ┌───────▼────────┐
                    │ Python 检测服务 │
                    │ (YOLOv8模型)   │
                    └────────────────┘

2.2 技术栈

模块 技术 说明
上位机界面 WPF (C#) 采用MVVM模式,实时显示图像和检测结果
运动控制 C# (模拟) Timer模拟脉冲输出和位置移动
相机模拟 C# 从本地文件夹循环读取图片,模拟视频流
检测服务 Python + YOLOv8 封装成命令行调用,接收图像路径返回JSON结果
通信方式 进程调用 C#启动Python进程,传递参数,读取标准输出

三、工业相机模拟(MockCameraService)

在没有真实工业相机的情况下,我们需要一个模拟相机,它能从本地文件夹循环读取图片,模拟相机采集过程。

3.1 创建C#模拟相机类

using System;
using System.IO;
using System.Timers;
using OpenCvSharp;

public class MockCameraService : IDisposable
{
    private Timer _timer;
    private string[] _imageFiles;
    private int _currentIndex = 0;
    private Action<Mat> _frameCallback;
    private bool _isGrabbing = false;

    /// <summary>
    /// 初始化模拟相机
    /// </summary>
    /// <param name="imageFolder">包含图片的文件夹路径</param>
    /// <param name="fps">模拟帧率</param>
    public MockCameraService(string imageFolder, double fps = 30)
    {
        if (!Directory.Exists(imageFolder))
            throw new DirectoryNotFoundException($"文件夹不存在: {imageFolder}");

        _imageFiles = Directory.GetFiles(imageFolder, "*.jpg");
        if (_imageFiles.Length == 0)
            _imageFiles = Directory.GetFiles(imageFolder, "*.png");
        if (_imageFiles.Length == 0)
            throw new Exception("文件夹中没有jpg或png图片");

        Array.Sort(_imageFiles); // 按文件名排序
        _timer = new Timer(1000 / fps);
        _timer.Elapsed += Timer_Elapsed;
        Console.WriteLine($"模拟相机初始化完成,加载 {_imageFiles.Length} 张图片");
    }

    /// <summary>
    /// 连接相机
    /// </summary>
    public bool Connect()
    {
        Console.WriteLine("模拟相机连接成功");
        return true;
    }

    /// <summary>
    /// 开始采集
    /// </summary>
    /// <param name="callback">每帧回调函数,接收Mat图像</param>
    public void StartGrabbing(Action<Mat> callback)
    {
        _frameCallback = callback;
        _isGrabbing = true;
        _timer.Start();
        Console.WriteLine("模拟相机开始采集");
    }

    /// <summary>
    /// 停止采集
    /// </summary>
    public void StopGrabbing()
    {
        _timer.Stop();
        _isGrabbing = false;
        Console.WriteLine("模拟相机停止采集");
    }

    private void Timer_Elapsed(object sender, ElapsedEventArgs e)
    {
        if (!_isGrabbing || _frameCallback == null) return;

        // 读取当前图片
        string imgPath = _imageFiles[_currentIndex];
        Mat frame = Cv2.ImRead(imgPath, ImreadModes.Color);
        if (frame.Empty())
        {
            Console.WriteLine($"读取图片失败: {imgPath}");
            return;
        }

        // 调用回调
        _frameCallback(frame);

        // 移动到下一张
        _currentIndex = (_currentIndex + 1) % _imageFiles.Length;
    }

    public void Dispose()
    {
        StopGrabbing();
        _timer?.Dispose();
    }
}

3.2 使用示例

// 在Main方法中测试
var camera = new MockCameraService(@"D:\test_images", 30);
camera.Connect();
camera.StartGrabbing(frame =>
{
    Cv2.ImShow("Mock Camera", frame);
    Cv2.WaitKey(1);
});

Console.WriteLine("按任意键停止...");
Console.ReadKey();
camera.StopGrabbing();
camera.Dispose();
Cv2.DestroyAllWindows();

四、运动控制模拟(MotionController)

运动控制是工业设备的核心,我们模拟雷赛运动控制卡的基本功能:移动到指定位置触发外部信号

4.1 创建模拟运动控制类

using System;
using System.Threading;

public class MotionController
{
    private int _currentPosition = 0;
    private readonly object _lock = new object();

    /// <summary>
    /// 移动到绝对位置(模拟脉冲输出)
    /// </summary>
    /// <param name="targetPosition">目标位置(单位:脉冲数或mm)</param>
    public bool MoveTo(int targetPosition)
    {
        Console.WriteLine($"[运动] 开始移动: 当前位置 {_currentPosition} -> 目标 {targetPosition}");

        // 模拟脉冲输出(这里用延时模拟)
        int distance = Math.Abs(targetPosition - _currentPosition);
        int steps = distance / 10; // 假设每10个脉冲移动1个单位
        for (int i = 0; i < steps; i++)
        {
            Thread.Sleep(10); // 每个脉冲耗时10ms
            lock (_lock)
            {
                if (_currentPosition < targetPosition)
                    _currentPosition += 10;
                else
                    _currentPosition -= 10;
            }
            // 可以在这里触发位置到达事件
        }

        lock (_lock)
        {
            _currentPosition = targetPosition;
        }
        Console.WriteLine($"[运动] 到达目标位置 {targetPosition}");
        return true;
    }

    /// <summary>
    /// 触发外部信号(模拟相机拍照触发)
    /// </summary>
    public void TriggerCamera()
    {
        Console.WriteLine("[运动] 发送拍照触发信号");
        // 实际项目中这里可能会触发一个硬件IO,或调用相机接口
    }

    /// <summary>
    /// 读取当前位置
    /// </summary>
    public int ReadPosition()
    {
        lock (_lock)
        {
            return _currentPosition;
        }
    }

    /// <summary>
    /// 归零
    /// </summary>
    public void Home()
    {
        Console.WriteLine("[运动] 开始归零");
        // 模拟归零过程
        Thread.Sleep(500);
        _currentPosition = 0;
        Console.WriteLine("[运动] 归零完成");
    }
}

4.2 使用示例

var motion = new MotionController();
motion.MoveTo(1000);
motion.TriggerCamera();
Console.WriteLine($"当前位置: {motion.ReadPosition()}");
motion.Home();

五、C#调用Python检测服务(封装)

我们采用进程调用方式,将YOLOv8检测服务封装成可重用的类。Python脚本接收图像路径,返回JSON格式的检测结果。

5.1 Python检测脚本(detect_defect.py)

import sys
import json
import cv2
from ultralytics import YOLO

def main():
    if len(sys.argv) < 2:
        print(json.dumps({"error": "缺少图像路径"}))
        return

    image_path = sys.argv[1]

    try:
        # 加载模型(实际项目中可全局加载一次,这里简化)
        model = YOLO('best.pt')  # 训练好的模型路径

        # 推理
        results = model(image_path)[0]

        detections = []
        if len(results.boxes) > 0:
            boxes = results.boxes.xyxy.cpu().numpy()
            confs = results.boxes.conf.cpu().numpy()
            classes = results.boxes.cls.cpu().numpy()

            for box, conf, cls in zip(boxes, confs, classes):
                detections.append({
                    "class_name": results.names[int(cls)],
                    "confidence": float(conf),
                    "x1": float(box[0]),
                    "y1": float(box[1]),
                    "x2": float(box[2]),
                    "y2": float(box[3])
                })

        print(json.dumps(detections))

    except Exception as e:
        print(json.dumps({"error": str(e)}))

if __name__ == "__main__":
    main()

5.2 C#封装类(PythonDetector.cs)

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Text.Json;

public class DefectResult
{
    public string ClassName { get; set; }
    public float Confidence { get; set; }
    public float X1 { get; set; }
    public float Y1 { get; set; }
    public float X2 { get; set; }
    public float Y2 { get; set; }

    public string BboxString => $"({X1:F0},{Y1:F0}) {X2 - X1:F0}x{Y2 - Y1:F0}";
}

public class PythonDetector
{
    private string _pythonExe;
    private string _scriptPath;

    /// <summary>
    /// 初始化检测器
    /// </summary>
    /// <param name="pythonExe">Python解释器路径(如:@"C:\Users\xxx\anaconda3\python.exe")</param>
    /// <param name="scriptPath">Python脚本路径</param>
    public PythonDetector(string pythonExe, string scriptPath)
    {
        _pythonExe = pythonExe;
        _scriptPath = scriptPath;
    }

    /// <summary>
    /// 检测单张图片
    /// </summary>
    /// <param name="imagePath">图像路径</param>
    /// <returns>检测结果列表</returns>
    public List<DefectResult> Detect(string imagePath)
    {
        var startInfo = new ProcessStartInfo
        {
            FileName = _pythonExe,
            Arguments = $"\"{_scriptPath}\" \"{imagePath}\"",
            UseShellExecute = false,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            CreateNoWindow = true,
            StandardOutputEncoding = Encoding.UTF8
        };

        using (Process process = Process.Start(startInfo))
        {
            string output = process.StandardOutput.ReadToEnd();
            string error = process.StandardError.ReadToEnd();
            process.WaitForExit();

            if (!string.IsNullOrEmpty(error))
                throw new Exception($"Python错误: {error}");

            try
            {
                // 尝试解析为错误对象
                var errorObj = JsonSerializer.Deserialize<Dictionary<string, string>>(output);
                if (errorObj != null && errorObj.ContainsKey("error"))
                    throw new Exception($"检测错误: {errorObj["error"]}");
            }
            catch { /* 不是错误对象,继续 */ }

            try
            {
                return JsonSerializer.Deserialize<List<DefectResult>>(output);
            }
            catch (Exception ex)
            {
                throw new Exception($"解析结果失败: {ex.Message}\n原始输出: {output}");
            }
        }
    }
}

5.3 使用示例

var detector = new PythonDetector(
    @"C:\Users\Administrator\anaconda3\python.exe",
    @"D:\PythonProject\detect_defect.py"
);

try
{
    var results = detector.Detect(@"D:\test.jpg");
    Console.WriteLine($"检测到 {results.Count} 个缺陷");
    foreach (var r in results)
    {
        Console.WriteLine($"  {r.ClassName}: {r.Confidence:P2} at {r.BboxString}");
    }
}
catch (Exception ex)
{
    Console.WriteLine($"检测失败: {ex.Message}");
}

六、WPF主界面开发(MVVM模式)

我们将创建一个简单的WPF窗口,包含开始/停止按钮、图像显示区域、缺陷列表和状态日志。

6.1 新建WPF项目

在Visual Studio中新建一个WPF应用(.NET Core 3.1或.NET 5/6/7/8均可),命名为DefectInspectionSystem

6.2 安装NuGet包

  • OpenCvSharp4(用于图像处理)
  • OpenCvSharp4.WpfExtensions(用于将Mat转为BitmapSource)

在包管理器控制台中执行:

Install-Package OpenCvSharp4
Install-Package OpenCvSharp4.WpfExtensions

6.3 主界面XAML(MainWindow.xaml)

<Window x:Class="DefectInspectionSystem.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="工业缺陷检测原型系统" Height="600" Width="900"
        WindowStartupLocation="CenterScreen">
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="250"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>

        <!-- 顶部控制栏 -->
        <StackPanel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" 
                    Orientation="Horizontal" Margin="0,0,0,10">
            <Button x:Name="BtnStart" Content="开始检测" Width="80" Height="30" Margin="5" Click="BtnStart_Click"/>
            <Button x:Name="BtnStop" Content="停止检测" Width="80" Height="30" Margin="5" Click="BtnStop_Click"/>
            <TextBlock Text="状态:" VerticalAlignment="Center" Margin="20,0,0,0"/>
            <TextBlock x:Name="TxtStatus" Text="就绪" VerticalAlignment="Center" Margin="5,0,0,0" Foreground="Green"/>
        </StackPanel>

        <!-- 左侧信息面板 -->
        <StackPanel Grid.Row="1" Grid.Column="0" Margin="5">
            <GroupBox Header="检测统计" Height="120">
                <StackPanel Margin="5">
                    <TextBlock x:Name="TxtTotal" Text="检测总数: 0"/>
                    <TextBlock x:Name="TxtDefect" Text="缺陷总数: 0" Margin="0,5,0,0"/>
                    <TextBlock x:Name="TxtPosition" Text="当前位置: 0" Margin="0,5,0,0"/>
                </StackPanel>
            </GroupBox>
            <GroupBox Header="缺陷列表" Height="300" Margin="0,10,0,0">
                <ListView x:Name="DefectListView">
                    <ListView.View>
                        <GridView>
                            <GridViewColumn Header="类型" DisplayMemberBinding="{Binding ClassName}" Width="60"/>
                            <GridViewColumn Header="置信度" DisplayMemberBinding="{Binding Confidence, StringFormat={}{0:P2}}" Width="80"/>
                            <GridViewColumn Header="位置" DisplayMemberBinding="{Binding BboxString}" Width="100"/>
                        </GridView>
                    </ListView.View>
                </ListView>
            </GroupBox>
        </StackPanel>

        <!-- 右侧图像显示 -->
        <Border Grid.Row="1" Grid.Column="1" BorderBrush="Gray" BorderThickness="1" Margin="5">
            <Image x:Name="ImgDisplay" Stretch="Uniform"/>
        </Border>

        <!-- 底部日志 -->
        <TextBox Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" 
                 x:Name="TxtLog" Height="80" Margin="5" 
                 IsReadOnly="True" VerticalScrollBarVisibility="Auto"
                 TextWrapping="Wrap"/>
    </Grid>
</Window>

6.4 后台代码(MainWindow.xaml.cs)

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using OpenCvSharp;
using OpenCvSharp.WpfExtensions;

namespace DefectInspectionSystem
{
    public partial class MainWindow : Window
    {
        private MockCameraService _camera;
        private MotionController _motion;
        private PythonDetector _detector;
        private CancellationTokenSource _cts;
        private ObservableCollection<DefectResult> _defects = new ObservableCollection<DefectResult>();
        private int _totalDetected = 0;
        private int _defectCount = 0;

        public MainWindow()
        {
            InitializeComponent();

            // 初始化服务
            string pythonExe = @"C:\Users\Administrator\anaconda3\python.exe"; // 修改为你的路径
            string scriptPath = @"D:\PythonProject\detect_defect.py";          // 修改为你的脚本路径
            string imageFolder = @"D:\test_images";                            // 模拟相机图片文件夹

            _detector = new PythonDetector(pythonExe, scriptPath);
            _camera = new MockCameraService(imageFolder, 10); // 10fps,放慢速度便于观察
            _camera.Connect();
            _motion = new MotionController();

            DefectListView.ItemsSource = _defects;
        }

        private async void BtnStart_Click(object sender, RoutedEventArgs e)
        {
            _cts = new CancellationTokenSource();
            SetStatus("检测中...", Colors.Orange);

            Log("开始检测流程");

            try
            {
                await Task.Run(() => InspectionLoop(_cts.Token));
            }
            catch (OperationCanceledException)
            {
                Log("检测被用户取消");
            }
            catch (Exception ex)
            {
                Log($"检测异常: {ex.Message}");
            }
            finally
            {
                SetStatus("就绪", Colors.Green);
            }
        }

        private void BtnStop_Click(object sender, RoutedEventArgs e)
        {
            _cts?.Cancel();
            _camera.StopGrabbing();
        }

        /// <summary>
        /// 检测主循环(模拟多工位)
        /// </summary>
        private void InspectionLoop(CancellationToken token)
        {
            // 模拟多个检测位置
            int[] positions = { 100, 200, 300, 400, 500 };

            _camera.StartGrabbing(frame =>
            {
                if (token.IsCancellationRequested)
                {
                    _camera.StopGrabbing();
                    return;
                }

                try
                {
                    // 显示当前帧
                    Dispatcher.Invoke(() =>
                    {
                        var bitmap = WriteableBitmapConverter.ToWriteableBitmap(frame);
                        ImgDisplay.Source = bitmap;
                    });

                    // 移动到一个位置(这里简化,实际应关联位置)
                    // 注意:不能在回调中执行耗时操作,但为了简单起见,我们在这里做检测
                    // 更好的做法是:检测用另一个线程
                    int currentPos = positions[new Random().Next(positions.Length)];
                    Dispatcher.Invoke(() => TxtPosition.Text = $"当前位置: {currentPos}");

                    // 模拟运动完成
                    _motion.MoveTo(currentPos);
                    _motion.TriggerCamera();

                    // 临时保存图像到文件(因为Python检测需要文件路径)
                    string tempFile = System.IO.Path.GetTempFileName() + ".jpg";
                    frame.SaveImage(tempFile);

                    // 调用检测
                    var results = _detector.Detect(tempFile);

                    // 更新UI
                    Dispatcher.Invoke(() =>
                    {
                        _totalDetected++;
                        _defects.Clear();
                        foreach (var r in results)
                        {
                            _defects.Add(r);
                        }
                        _defectCount += results.Count;

                        TxtTotal.Text = $"检测总数: {_totalDetected}";
                        TxtDefect.Text = $"缺陷总数: {_defectCount}";

                        Log($"位置 {currentPos}: 检测到 {results.Count} 个缺陷");
                    });

                    // 模拟分选动作
                    if (results.Count > 0)
                    {
                        // 触发分选(此处可调用运动控制)
                        Console.WriteLine("触发分选");
                    }

                    // 清理临时文件
                    try { System.IO.File.Delete(tempFile); } catch { }
                }
                catch (Exception ex)
                {
                    Dispatcher.Invoke(() => Log($"处理帧异常: {ex.Message}"));
                }
            });

            // 模拟运行一段时间(实际应等待用户停止)
            while (!token.IsCancellationRequested)
            {
                Thread.Sleep(100);
            }
        }

        private void SetStatus(string status, Color color)
        {
            Dispatcher.Invoke(() =>
            {
                TxtStatus.Text = status;
                TxtStatus.Foreground = new SolidColorBrush(color);
            });
        }

        private void Log(string message)
        {
            Dispatcher.Invoke(() =>
            {
                TxtLog.AppendText($"[{DateTime.Now:HH:mm:ss}] {message}\n");
                TxtLog.ScrollToEnd();
            });
        }

        protected override void OnClosed(EventArgs e)
        {
            _camera?.StopGrabbing();
            _camera?.Dispose();
            _cts?.Cancel();
            base.OnClosed(e);
        }
    }
}

6.5 解决跨线程UI更新问题

WPF中非UI线程不能直接更新UI控件,我们使用Dispatcher.Invoke来安全更新。


七、完整项目结构

DefectInspectionSystem/
├── DefectInspectionSystem.csproj
├── MainWindow.xaml
├── MainWindow.xaml.cs
├── Services/
│   ├── MockCameraService.cs
│   ├── MotionController.cs
│   └── PythonDetector.cs
├── Models/
│   └── DefectResult.cs
├── PythonScripts/
│   └── detect_defect.py
└── test_images/               # 模拟图片文件夹
    ├── img1.jpg
    ├── img2.jpg
    └── ...

7.1 在Visual Studio中组织

  1. 创建Services文件夹,添加上述三个服务类。
  2. 创建Models文件夹,添加DefectResult.cs
  3. 创建PythonScripts文件夹,将detect_defect.py和训练好的best.pt放入其中。
  4. bin/Debug/netX.X/下创建test_images文件夹,放入一些测试图片。

八、运行与测试

  1. 确保Python环境可用,且已安装ultralyticsopencv-python
  2. 将训练好的YOLOv8模型best.pt放在Python脚本同目录。
  3. 修改代码中的Python解释器路径和脚本路径为实际路径。
  4. 按F5运行,点击“开始检测”,观察界面实时显示图像和检测结果。

九、项目亮点与简历撰写

9.1 项目亮点

  • 全流程闭环:从运动控制、图像采集、算法检测到分选逻辑,模拟真实工业场景。
  • 跨语言协作:C#负责界面和运动控制,Python负责深度学习检测,充分发挥各自优势。
  • 模块化设计:相机、运动、检测均为独立服务,易于替换真实硬件。
  • 模拟硬件:无真实相机/板卡仍可开发调试,节约成本。

9.2 简历描述模板

工业缺陷检测原型系统(2025.03 - 至今)
技术栈:C# · WPF · Python · PyTorch · YOLOv8 · OpenCV

  • 项目概述:设计并实现一套模拟工业环境的硅片表面缺陷检测系统,包含运动控制、图像采集、深度学习检测、结果展示四大模块。
  • 核心贡献
    • 基于YOLOv8训练表面缺陷检测模型,在NEU数据集上达到mAP50 0.95,支持划痕、脏污、裂纹等4类缺陷识别。
    • 使用C# WPF开发上位机界面,采用MVVM模式,实时显示检测图像和缺陷列表。
    • 通过进程调用实现C#与Python混合编程,Python负责模型推理,C#负责流程控制。
    • 模拟运动控制卡,实现多工位自动检测与分选逻辑,单次检测周期控制在2秒内。
    • 设计MockCameraService模拟工业相机采图,无需硬件即可完成全流程开发测试。

9.3 GitHub上传建议

  • 创建仓库DefectInspectionSystem
  • 编写README.md,包含项目简介、技术栈、运行截图、如何运行
  • 忽略不需要的文件(如.vsbinobj
  • 附上一张界面截图

十、总结与下一步

通过本阶段的学习,你已经成功构建了一个工业缺陷检测原型系统,它集成了:

  • ✅ Python深度学习模型(YOLOv8)
  • ✅ C# WPF上位机界面
  • ✅ 模拟相机和运动控制
  • ✅ 完整的检测流程

下一步:进入第七阶段——AI模型部署,学习如何将模型转换为ONNX、使用TensorRT/OpenVINO加速,并在边缘设备上运行。


📚 参考文档链接


如果在实现过程中遇到任何问题,欢迎随时交流!下一阶段我们将进入更深入的AI模型部署,敬请期待。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐