image_anomaly_detection.md 32 KB


comments: true

图像异常检测产线使用教程

1. 图像异常检测产线介绍

图像异常检测是一种通过分析图像中的内容,来识别与众不同或不符合正常模式的图像处理技术。图像异常检测能够自动识别出图像中潜在的缺陷、异常或异常行为,从而帮助我们及时发现问题并采取相应措施。

本产线集成了高精度的异常检测模型 STFPM,提取图像中的异常或缺陷区域,使用场景覆盖工业制造、食品外观质检以及医疗影像分析等各个领域。本产线同时提供了灵活的服务化部署方式,支持在多种硬件上使用多种编程语言调用。不仅如此,本产线也提供了二次开发的能力,您可以基于本产线在您自己的数据集上训练调优,训练后的模型也可以无缝集成。

图像异常检测产线中包含图像异常检测模块,包含的模型如下。

模型模型下载链接 mIoU(%) 模型存储大小(M)
STFPM推理模型/训练模型 99.01 21.5 M

测试环境说明:

      <li><b>性能测试环境</b>
          <ul>
          <li><strong>测试数据集:
             </strong>
              <b> <a href="https://www.mvtec.com/company/research/datasets/mvtec-ad">MVTec AD</a></b> 验证集 grid 数据。
             </li>
              <li><strong>硬件配置:</strong>
                  <ul>
                      <li>GPU:NVIDIA Tesla T4</li>
                      <li>CPU:Intel Xeon Gold 6271C @ 2.60GHz</li>
                      <li>其他环境:Ubuntu 20.04 / cuDNN 8.6 / TensorRT 8.5.2.2</li>
                  </ul>
              </li>
          </ul>
      </li>
      <li><b>推理模式说明</b></li>
    

    模式 GPU配置 CPU配置 加速技术组合
    常规模式 FP32精度 / 无TRT加速 FP32精度 / 8线程 PaddleInference
    高性能模式 选择先验精度类型和加速策略的最优组合 FP32精度 / 8线程 选择先验最优后端(Paddle/OpenVINO/TRT等)

    2. 快速开始

    PaddleX 所提供的模型产线均可以快速体验效果,您可以在本地使用命令行或 Python 体验图像异常检测产线的效果。

    在本地使用图像异常检测产线前,请确保您已经按照PaddleX本地安装教程完成了PaddleX的wheel包安装。如果您希望选择性安装依赖,请参考安装教程中的相关说明。该产线对应的依赖分组为 cv

    2.1 命令行方式体验

    一行命令即可快速体验图像异常检测产线效果,使用 测试文件,并将 --input 替换为本地路径,进行预测

    paddlex --pipeline anomaly_detection --input uad_grid.png --device gpu:0  --save_path ./output
    

    相关的参数说明可以参考2.1.2 Python脚本方式集成中的参数说明。

    运行后,会将结果打印到终端上,结果如下:

    {'input_path': 'uad_grid.png', 'pred': '...'}

    运行结果参数说明可以参考2.1.2 Python脚本方式集成中的结果解释。

    可视化结果保存在save_path下,可视化结果如下:

    2.2 Python脚本方式集成

    上述命令行是为了快速体验查看效果,一般来说,在项目中,往往需要通过代码集成,您可以通过几行代码即可完成产线的快速推理,推理代码如下:

    from paddlex import create_pipeline
    
    pipeline = create_pipeline(pipeline="anomaly_detection")
    output = pipeline.predict(input="uad_grid.png")
    for res in output:
        res.print() ## 打印预测的结构化输出
        res.save_to_img(save_path="./output/") ## 保存结果可视化图像
        res.save_to_json(save_path="./output/") ## 保存预测的结构化输出
    

    在上述 Python 脚本中,执行了如下几个步骤:

    (1)通过 create_pipeline() 实例化产线对象:具体参数说明如下:

    参数 参数说明 参数类型 默认值
    pipeline 产线名称或是产线配置文件路径。如为产线名称,则必须为 PaddleX 所支持的产线。 str
    config 产线具体的配置信息(如果和pipeline同时设置,优先级高于pipeline,且要求产线名和pipeline一致)。 dict[str, Any] None
    device 产线推理设备。支持指定GPU具体卡号,如“gpu:0”,其他硬件具体卡号,如“npu:0”,CPU如“cpu”。 str gpu:0
    use_hpip 是否启用高性能推理,仅当该产线支持高性能推理时可用。 bool False

    (2)调用 anomaly_detection 产线对象的 predict() 方法进行推理预测。该方法将返回一个 generator。以下是 predict() 方法的参数及其说明:

    参数 参数说明 参数类型 可选项 默认值
    input 待预测数据,支持多种输入类型,必填 Python Var|str|list
    • Python Var:如 numpy.ndarray 表示的图像数据
    • str:如图像文件的本地路径:/root/data/img.jpg如URL链接,如图像文件的网络URL:示例如本地目录,该目录下需包含待预测图像,如本地路径:/root/data/
    • List:列表元素需为上述类型数据,如[numpy.ndarray, numpy.ndarray][\"/root/data/img1.jpg\", \"/root/data/img2.jpg\"][\"/root/data1\", \"/root/data2\"]
    None
    device 产线推理设备 str|None
    • CPU:如 cpu 表示使用 CPU 进行推理;
    • GPU:如 gpu:0 表示使用第 1 块 GPU 进行推理;
    • NPU:如 npu:0 表示使用第 1 块 NPU 进行推理;
    • XPU:如 xpu:0 表示使用第 1 块 XPU 进行推理;
    • MLU:如 mlu:0 表示使用第 1 块 MLU 进行推理;
    • DCU:如 dcu:0 表示使用第 1 块 DCU 进行推理;
    • None:如果设置为 None, 将默认使用产线初始化的该参数值,初始化时,会优先使用本地的 GPU 0号设备,如果没有,则使用 CPU 设备;
    None

    (3)对预测结果进行处理,每个样本的预测结果均为对应的Result对象,且支持打印、保存为图片、保存为json文件的操作:

    方法 方法说明 参数 参数类型 参数说明 默认值
    print() 打印结果到终端 format_json bool 是否对输出内容进行使用 JSON 缩进格式化 True
    indent int 指定缩进级别,以美化输出的 JSON 数据,使其更具可读性,仅当 format_jsonTrue 时有效 4
    ensure_ascii bool 控制是否将非 ASCII 字符转义为 Unicode。设置为 True 时,所有非 ASCII 字符将被转义;False 则保留原始字符,仅当format_jsonTrue时有效 False
    save_to_json() 将结果保存为json格式的文件 save_path str 保存的文件路径,当为目录时,保存文件命名与输入文件类型命名一致
    indent int 指定缩进级别,以美化输出的 JSON 数据,使其更具可读性,仅当 format_jsonTrue 时有效 4
    ensure_ascii bool 控制是否将非 ASCII 字符转义为 Unicode。设置为 True 时,所有非 ASCII 字符将被转义;False 则保留原始字符,仅当format_jsonTrue时有效 False
    save_to_img() 将结果保存为图像格式的文件 save_path str 保存的文件路径,支持目录或文件路径
    • 调用print() 方法会将结果打印到终端,打印到终端的内容解释如下:

      • input_path: (str) 待预测图像的输入路径

      • pred: (str) 预测结果,由于像素值较多,此处用...代替,不进行打印。

    • 调用save_to_json() 方法会将上述内容保存到指定的save_path中,如果指定为目录,则保存的路径为save_path/{your_img_basename}_res.json,如果指定为文件,则直接保存到该文件中。由于json文件不支持保存numpy数组,因此会将其中的numpy.array类型转换为列表形式。

    • 调用save_to_img() 方法会将可视化结果保存到指定的save_path中,如果指定为目录,则保存的路径为save_path/{your_img_basename}_res.{your_img_extension},如果指定为文件,则直接保存到该文件中。(产线通常包含较多结果图片,不建议直接指定为具体的文件路径,否则多张图会被覆盖,仅保留最后一张图)

    • 此外,也支持通过属性获取带结果的可视化图像和预测结果,具体如下:

    属性 属性说明
    json 获取预测的 json 格式的结果
    img 获取格式为 dict 的可视化图像
    • json 属性获取的预测结果为dict类型的数据,相关内容与调用 save_to_json() 方法保存的内容一致。
    • img 属性返回的预测结果是一个字典类型的数据。其中,键为 res,对应的值是 Image.Image 对象:用于显示 anomaly_detection 结果的可视化图像。

    此外,您可以获取 anomaly_detection 产线配置文件,并加载配置文件进行预测。可执行如下命令将结果保存在 my_path 中:

    paddlex --get_pipeline_config anomaly_detection --save_path ./my_path
    

    若您获取了配置文件,即可对图像异常检测产线各项配置进行自定义,只需要修改 create_pipeline 方法中的 pipeline 参数值为产线配置文件路径即可。

    例如,若您的配置文件保存在 ./my_path/*anomaly_detection.yaml ,则只需执行:

    from paddlex import create_pipeline
    pipeline = create_pipeline(pipeline="./my_path/anomaly_detection.yaml")
    output = pipeline.predict("uad_grid.png")
    for res in output:
        res.print() ## 打印预测的结构化输出
        res.save_to_img("./output/") ## 保存结果可视化图像
        res.save_to_json("./output/") ## 保存预测的结构化输出
    

    3. 开发集成/部署

    如果产线可以达到您对产线推理速度和精度的要求,您可以直接进行开发集成/部署。

    若您需要将产线直接应用在您的Python项目中,可以参考 2.2 Python脚本方式中的示例代码。

    此外,PaddleX 也提供了其他三种部署方式,详细说明如下:

    🚀 高性能推理:在实际生产环境中,许多应用对部署策略的性能指标(尤其是响应速度)有着较严苛的标准,以确保系统的高效运行与用户体验的流畅性。为此,PaddleX 提供高性能推理插件,旨在对模型推理及前后处理进行深度性能优化,实现端到端流程的显著提速,详细的高性能推理流程请参考PaddleX高性能推理指南

    ☁️ 服务化部署:服务化部署是实际生产环境中常见的一种部署形式。通过将推理功能封装为服务,客户端可以通过网络请求来访问这些服务,以获取推理结果。PaddleX 支持多种产线服务化部署方案,详细的产线服务化部署流程请参考PaddleX服务化部署指南

    以下是基础服务化部署的API参考与多语言服务调用示例:

    API参考

    对于服务提供的主要操作:

    • HTTP请求方法为POST。
    • 请求体和响应体均为JSON数据(JSON对象)。
    • 当请求处理成功时,响应状态码为200,响应体的属性如下:
    名称 类型 含义
    logId string 请求的UUID。
    errorCode integer 错误码。固定为0
    errorMsg string 错误说明。固定为"Success"
    result object 操作结果。
    • 当请求处理未成功时,响应体的属性如下:
    名称 类型 含义
    logId string 请求的UUID。
    errorCode integer 错误码。与响应状态码相同。
    errorMsg string 错误说明。

    服务提供的主要操作如下:

    • infer

    对图像进行异常检测。

    POST /anomaly-detection

    • 请求体的属性如下:
    名称 类型 含义 是否必填
    image string | null 服务器可访问的图像文件的URL或图像文件内容的Base64编码结果。
    • 请求处理成功时,响应体的result具有如下属性:
    名称 类型 含义
    labelMap array 记录图像中每个像素的类别标签(按照行优先顺序排列)。其中255表示异常点,0表示非异常点。
    size array 图像形状。数组中元素依次为图像的高度和宽度。
    image string 异常检测结果图。图像为JPEG格式,使用Base64编码。

    result示例如下:

    {
    "labelMap": [
    0,
    0,
    255,
    0
    ],
    "size": [
    2,
    2
    ],
    "image": "xxxxxx"
    }
    
    多语言调用服务示例
    Python
    import base64
    import requests
    
    API_URL = "http://localhost:8080/image-anomaly-detection" # 服务URL
    image_path = "./demo.jpg"
    output_image_path = "./out.jpg"
    
    # 对本地图像进行Base64编码
    with open(image_path, "rb") as file:
        image_bytes = file.read()
        image_data = base64.b64encode(image_bytes).decode("ascii")
    
    payload = {"image": image_data}  # Base64编码的文件内容或者图像URL
    
    # 调用API
    response = requests.post(API_URL, json=payload)
    
    # 处理接口返回数据
    assert response.status_code == 200
    result = response.json()["result"]
    with open(output_image_path, "wb") as file:
        file.write(base64.b64decode(result["image"]))
    print(f"Output image saved at {output_image_path}")
    # result.labelMap 记录图像中每个像素的类别标签(按照行优先顺序排列)详见API参考文档
    
    C++
    #include <iostream>
    #include "cpp-httplib/httplib.h" // https://github.com/Huiyicc/cpp-httplib
    #include "nlohmann/json.hpp" // https://github.com/nlohmann/json
    #include "base64.hpp" // https://github.com/tobiaslocker/base64
    
    int main() {
        httplib::Client client("localhost:8080");
        const std::string imagePath = "./demo.jpg";
        const std::string outputImagePath = "./out.jpg";
    
        httplib::Headers headers = {
            {"Content-Type", "application/json"}
        };
    
        // 对本地图像进行Base64编码
        std::ifstream file(imagePath, std::ios::binary | std::ios::ate);
        std::streamsize size = file.tellg();
        file.seekg(0, std::ios::beg);
    
        std::vector<char> buffer(size);
        if (!file.read(buffer.data(), size)) {
            std::cerr << "Error reading file." << std::endl;
            return 1;
        }
        std::string bufferStr(reinterpret_cast<const char*>(buffer.data()), buffer.size());
        std::string encodedImage = base64::to_base64(bufferStr);
    
        nlohmann::json jsonObj;
        jsonObj["image"] = encodedImage;
        std::string body = jsonObj.dump();
    
        // 调用API
        auto response = client.Post("/image-anomaly-detection", headers, body, "application/json");
        // 处理接口返回数据
        if (response && response->status == 200) {
            nlohmann::json jsonResponse = nlohmann::json::parse(response->body);
            auto result = jsonResponse["result"];
    
            encodedImage = result["image"];
            std::string decodedString = base64::from_base64(encodedImage);
            std::vector<unsigned char> decodedImage(decodedString.begin(), decodedString.end());
            std::ofstream outputImage(outPutImagePath, std::ios::binary | std::ios::out);
            if (outputImage.is_open()) {
                outputImage.write(reinterpret_cast<char*>(decodedImage.data()), decodedImage.size());
                outputImage.close();
                std::cout << "Output image saved at " << outPutImagePath << std::endl;
                // result.labelMap 记录图像中每个像素的类别标签(按照行优先顺序排列)详见API参考文档
            } else {
                std::cerr << "Unable to open file for writing: " << outPutImagePath << std::endl;
            }
        } else {
            std::cout << "Failed to send HTTP request." << std::endl;
            return 1;
        }
    
        return 0;
    }
    
    Java
    import okhttp3.*;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.JsonNode;
    import com.fasterxml.jackson.databind.node.ObjectNode;
    
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.util.Base64;
    
    public class Main {
        public static void main(String[] args) throws IOException {
            String API_URL = "http://localhost:8080/image-anomaly-detection"; // 服务URL
            String imagePath = "./demo.jpg"; // 本地图像
            String outputImagePath = "./out.jpg"; // 输出图像
    
            // 对本地图像进行Base64编码
            File file = new File(imagePath);
            byte[] fileContent = java.nio.file.Files.readAllBytes(file.toPath());
            String imageData = Base64.getEncoder().encodeToString(fileContent);
    
            ObjectMapper objectMapper = new ObjectMapper();
            ObjectNode params = objectMapper.createObjectNode();
            params.put("image", imageData); // Base64编码的文件内容或者图像URL
    
            // 创建 OkHttpClient 实例
            OkHttpClient client = new OkHttpClient();
            MediaType JSON = MediaType.Companion.get("application/json; charset=utf-8");
            RequestBody body = RequestBody.Companion.create(params.toString(), JSON);
            Request request = new Request.Builder()
                    .url(API_URL)
                    .post(body)
                    .build();
    
            // 调用API并处理接口返回数据
            try (Response response = client.newCall(request).execute()) {
                if (response.isSuccessful()) {
                    String responseBody = response.body().string();
                    JsonNode resultNode = objectMapper.readTree(responseBody);
                    JsonNode result = resultNode.get("result");
                    String base64Image = result.get("image").asText();
                    JsonNode labelMap = result.get("labelMap");
    
                    byte[] imageBytes = Base64.getDecoder().decode(base64Image);
                    try (FileOutputStream fos = new FileOutputStream(outputImagePath)) {
                        fos.write(imageBytes);
                    }
                    System.out.println("Output image saved at " + outputImagePath);
                    // result.labelMap 记录图像中每个像素的类别标签(按照行优先顺序排列)详见API参考文档
                } else {
                    System.err.println("Request failed with code: " + response.code());
                }
            }
        }
    }
    
    Go
    package main
    
    import (
        "bytes"
        "encoding/base64"
        "encoding/json"
        "fmt"
        "io/ioutil"
        "net/http"
    )
    
    func main() {
        API_URL := "http://localhost:8080/image-anomaly-detection"
        imagePath := "./demo.jpg"
        outputImagePath := "./out.jpg"
    
        // 对本地图像进行Base64编码
        imageBytes, err := ioutil.ReadFile(imagePath)
        if err != nil {
            fmt.Println("Error reading image file:", err)
            return
        }
        imageData := base64.StdEncoding.EncodeToString(imageBytes)
    
        payload := map[string]string{"image": imageData} // Base64编码的文件内容或者图像URL
        payloadBytes, err := json.Marshal(payload)
        if err != nil {
            fmt.Println("Error marshaling payload:", err)
            return
        }
    
        // 调用API
        client := &http.Client{}
        req, err := http.NewRequest("POST", API_URL, bytes.NewBuffer(payloadBytes))
        if err != nil {
            fmt.Println("Error creating request:", err)
            return
        }
    
        res, err := client.Do(req)
        if err != nil {
            fmt.Println("Error sending request:", err)
            return
        }
        defer res.Body.Close()
    
        // 处理接口返回数据
        body, err := ioutil.ReadAll(res.Body)
        if err != nil {
            fmt.Println("Error reading response body:", err)
            return
        }
        type Response struct {
            Result struct {
                Image      string   `json:"image"`
                Labelmap []map[string]interface{} `json:"labelMap"`
            } `json:"result"`
        }
        var respData Response
        err = json.Unmarshal([]byte(string(body)), &respData)
        if err != nil {
            fmt.Println("Error unmarshaling response body:", err)
            return
        }
    
        outputImageData, err := base64.StdEncoding.DecodeString(respData.Result.Image)
        if err != nil {
            fmt.Println("Error decoding base64 image data:", err)
            return
        }
        err = ioutil.WriteFile(outputImagePath, outputImageData, 0644)
        if err != nil {
            fmt.Println("Error writing image to file:", err)
            return
        }
        fmt.Printf("Image saved at %s.jpg\n", outputImagePath)
        // result.labelMap 记录图像中每个像素的类别标签(按照行优先顺序排列)详见API参考文档
    }
    
    C#
    using System;
    using System.IO;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Text;
    using System.Threading.Tasks;
    using Newtonsoft.Json.Linq;
    
    class Program
    {
        static readonly string API_URL = "http://localhost:8080/image-anomaly-detection";
        static readonly string imagePath = "./demo.jpg";
        static readonly string outputImagePath = "./out.jpg";
    
        static async Task Main(string[] args)
        {
            var httpClient = new HttpClient();
    
            // 对本地图像进行Base64编码
            byte[] imageBytes = File.ReadAllBytes(imagePath);
            string image_data = Convert.ToBase64String(imageBytes);
    
            var payload = new JObject{ { "image", image_data } }; // Base64编码的文件内容或者图像URL
            var content = new StringContent(payload.ToString(), Encoding.UTF8, "application/json");
    
            // 调用API
            HttpResponseMessage response = await httpClient.PostAsync(API_URL, content);
            response.EnsureSuccessStatusCode();
    
            // 处理接口返回数据
            string responseBody = await response.Content.ReadAsStringAsync();
            JObject jsonResponse = JObject.Parse(responseBody);
    
            string base64Image = jsonResponse["result"]["image"].ToString();
            byte[] outputImageBytes = Convert.FromBase64String(base64Image);
    
            File.WriteAllBytes(outputImagePath, outputImageBytes);
            Console.WriteLine($"Output image saved at {outputImagePath}");
            // result.labelMap 记录图像中每个像素的类别标签(按照行优先顺序排列)详见API参考文档
        }
    }
    
    Node.js
    const axios = require('axios');
    const fs = require('fs');
    
    const API_URL = 'http://localhost:8080/image-anomaly-detection'
    const imagePath = './demo.jpg'
    const outputImagePath = "./out.jpg";
    
    let config = {
       method: 'POST',
       maxBodyLength: Infinity,
       url: API_URL,
       data: JSON.stringify({
        'image': encodeImageToBase64(imagePath)  // Base64编码的文件内容或者图像URL
      })
    };
    
    // 对本地图像进行Base64编码
    function encodeImageToBase64(filePath) {
      const bitmap = fs.readFileSync(filePath);
      return Buffer.from(bitmap).toString('base64');
    }
    
    // 调用API
    axios.request(config)
    .then((response) => {
        // 处理接口返回数据
        const result = response.data["result"];
        const imageBuffer = Buffer.from(result["image"], 'base64');
        fs.writeFile(outputImagePath, imageBuffer, (err) => {
          if (err) throw err;
          console.log(`Output image saved at ${outputImagePath}`);
        });
        // result.labelMap 记录图像中每个像素的类别标签(按照行优先顺序排列)详见API参考文档
    })
    .catch((error) => {
      console.log(error);
    });
    

    PHP

    <?php

    $API_URL = "http://localhost:8080/image-anomaly-detection"; // 服务URL $image_path = "./demo.jpg"; $output_image_path = "./out.jpg";

    // 对本地图像进行Base64编码 $image_data = base64_encode(file_get_contents($image_path)); $payload = array("image" => $image_data); // Base64编码的文件内容或者图像URL

    // 调用API $ch = curl_init($API_URL); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json')); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); curl_close($ch);

    // 处理接口返回数据 $result = json_decode($response, true)["result"]; file_put_contents($output_image_path, base64_decode($result["image"])); echo "Output image saved at " . $output_image_path . "\n"; // result.labelMap 记录图像中每个像素的类别标签(按照行优先顺序排列)详见API参考文档 ?>


    📱 端侧部署:端侧部署是一种将计算和数据处理功能放在用户设备本身上的方式,设备可以直接处理数据,而不需要依赖远程的服务器。PaddleX 支持将模型部署在 Android 等端侧设备上,详细的端侧部署流程请参考PaddleX端侧部署指南。 您可以根据需要选择合适的方式部署模型产线,进而进行后续的 AI 应用集成。

    4. 二次开发

    如果图像异常检测产线提供的默认模型权重在您的场景中,精度或速度不满意,您可以尝试利用您自己拥有的特定领域或应用场景的数据对现有模型进行进一步的微调,以提升图像异常检测产线的在您的场景中的识别效果。

    4.1 模型微调

    由于图像异常检测产线包含无监督图像异常检测模块,如果模型产线的效果不及预期,那么您需要参考无监督异常检测模块开发教程中的二次开发章节,使用您的私有数据集对图像异常检测模型进行微调。

    4.2 模型应用

    当您使用私有数据集完成微调训练后,可获得本地模型权重文件。

    若您需要使用微调后的模型权重,只需对产线配置文件做修改,将微调后模型权重的本地路径填写至产线配置文件中的 model_dir 即可:

    pipeline_name: anomaly_detection
    
    SubModules:
      AnomalyDetection:
        module_name: anomaly_detection
        model_name: STFPM
        model_dir: null  # 替换为微调后的图像异常检测模型权重路径
        batch_size: 1
    

    随后, 参考2. 快速开始中的命令行方式或Python脚本方式,加载修改后的产线配置文件即可。

    5. 多硬件支持

    PaddleX 支持英伟达 GPU、昆仑芯 XPU、昇腾 NPU和寒武纪 MLU 等多种主流硬件设备,仅需修改 --device 参数即可完成不同硬件之间的无缝切换。

    例如,您使用昇腾 NPU 进行图像异常检测产线的推理,使用的 CLI 命令为:

    paddlex --pipeline anomaly_detection --input uad_grid.png --device npu:0
    

    当然,您也可以在 Python 脚本中 create_pipeline() 时或者 predict() 时指定硬件设备。

    若您想在更多种类的硬件上使用图像异常检测产线,请参考PaddleX多硬件使用指南