# GRAY_CNN(图片灰度化处理+图片识别)

## 应用概述

> 这里将以图片灰度化加图片识别来介绍AI+DSP应用开发的开发流程。 其中图片灰度化可以使用DSP来完成，图片识别则使用AI来完成。 灰度化处理使用灰度化公式来进行，图片识别使用CNN模型。

## 开发流程

### 1. 定义模型

> - 图片灰度化处理过程。

> 这里实现图片灰度化，使用蓝、绿、红三个通道的值进行加权求和，计算出一个灰度值。 这里使用的权重分别是0.114、0.587和0.299，这些数值是基于人眼对不同颜色的敏感度来选择的， 用于将彩色图像转换为灰度图像。公式为：

> \[Gray = B \times 0.114 + G \times 0.587 + R \times 0.299\]

> 以及定义一个Clip操作，确保灰度值在0到255之间。 代码示例如下：

>      1import mindspore as ms
>      2from mindspore.train.serialization import export
>      3import numpy as np
>      4from mindspore import nn, ops
>      5
>      6class Gray(nn.Cell):
>      7    def __init__(self):
>      8        super(Gray, self).__init__()
>      9        self.split = ops.Split(axis=2, output_num=3)
>     10    def construct(self, x):
>     11        x = self.split(x)
>     12        b, g, r = x
>     13        x = (
>     14            b * 0.114 +
>     15            g * 0.587 +
>     16            r * 0.299
>     17        ).squeeze(-1)
>     18        x = ops.clip_by_value(x, 0, 255)
>     19        return x

> - 定义AI模型，用于图片识别。

> 这里的AI模型是卷积神经网络（CNN）。CNN主要由卷积，池化，激活等算子组成； CNN模型架构如下表所示：

> 层级 | 操作类型 | 参数细节 | 输出维度
> --- | --- | --- | ---
> 输入层 | 灰度图像输入 | 1通道，尺寸 H×W | H×W×1
> 卷积块1 | Conv2d | 输入通道:1 → 输出通道:32, 卷积核:3×3 | H×W×32
> | ReLU激活 | 非线性变换 | H×W×32
> | MaxPool2d | 窗口:2×2, 步长:2 | H/2×W/2×32
> 卷积块2 | Conv2d | 输入通道:32 → 输出通道:64, 卷积核:3×3 | H/2×W/2×64
> | ReLU激活 | 非线性变换 | H/2×W/2×64
> | MaxPool2d | 窗口:2×2, 步长:2 | H/4×W/4×64
> 卷积块3 | Conv2d | 输入通道:64 → 输出通道:128, 卷积核:3×3 | H/4×W/4×128
> | ReLU激活 | 非线性变换 | H/4×W/4×128
> | MaxPool2d | 窗口:2×2, 步长:2 | H/8×W/8×128
> 全连接层 | Flatten | 展平多维特征图 | 32768 (H/8×W/8×128)
> | Dense | 输入:32768 → 输出:64, 激活:ReLU | 64
> | Dense (输出层) | 输入:64 → 输出:5, 无激活 | 5
> 输出层 | 分类结果 | 5类概率分布 | 5

> CNN模型用于图片识别，不能直接导出模型,需要训练模型，该部分内容见: 2. 模型训练

> 定义CNN模型的代码示例如下：

>      1import mindspore as ms
>      2from mindspore import nn
>      3class CNN(nn.Cell):
>      4def __init__(self):
>      5    super().__init__()
>      6    self.conv1 = nn.Conv2d(1, 32, kernel_size=3, has_bias=True)
>      7    self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)
>      8    self.conv2 = nn.Conv2d(32, 64, kernel_size=3, has_bias=True)
>      9    self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)
>     10    self.conv3 = nn.Conv2d(64, 128, kernel_size=3, has_bias=True)
>     11    self.pool3 = nn.MaxPool2d(kernel_size=2, stride=2)
>     12    self.flatten = nn.Flatten()
>     13    self.dense1 = nn.Dense(32768, 64)
>     14    self.dense2 = nn.Dense(64, 5)
>     15    self.relu = nn.ReLU()
>     16
>     17def construct(self, x):
>     18    x = self.relu(self.conv1(x))
>     19    x = self.pool1(x)
>     20    x = self.relu(self.conv2(x))
>     21    x = self.pool2(x)
>     22    x = self.relu(self.conv3(x))
>     23    x = self.pool3(x)
>     24    x = self.flatten(x)
>     25    x = self.relu(self.dense1(x))
>     26    x = self.dense2(x)
>     27    return x

### 2. 模型训练

> CNN模型需要进行训练才能正确识别，训练需要数据集，这里已经提前准备好数据集，放在Target文件夹下， 使用MindSpore框架进行模型训练，需要导入相关库和模块，定义数据预处理、模型结构、损失函数和优化器等。 重新组网时，直接使用 `nn.GraphCell()` 接口会导致权重丢失， 可以在训练前时使用 `ms.save_checkpoint()` 接口保存成ckpt文件， 重新组网时，使用 `ms.load_checkpoint()` 接口加载ckpt文件即可。 以下代码展示了如何加载数据集，进行10次模型训练，以及导出模型。 训练以及导出模型代码如下：

>      1import mindspore as ms
>      2from mindspore.train.serialization import export
>      3from mindspore import nn, context
>      4import numpy as np
>      5from PIL import Image
>      6import mindspore.dataset as ds
>      7from mindspore.dataset import py_transforms
>      8import mindspore.dataset.vision as CV
>      9from mindspore.train.callback import  LossMonitor
>     10
>     11batch_size = 32
>     12img_size = (128, 128)
>     13data_path = './Target'
>     14
>     15# 数据预处理
>     16def rescale_to_0_1(image):
>     17    return image / 255.0
>     18
>     19# 自定义函数，添加 color 通道维度
>     20def add_channels(image):
>     21    if len(image.shape) == 2:  # 单个图像，没有 cin_channel 维度
>     22        image_four_channels = np.expand_dims(image, axis=0)
>     23    else:
>     24        pass
>     25        return image
>     26    return image_four_channels
>     27
>     28def export_cnn():
>     29    context.set_context(mode=context.PYNATIVE_MODE, device_target="CPU")
>     30    resize_op = CV.Resize(img_size)
>     31    rescale_transform = ms.dataset.transforms.Compose([rescale_to_0_1])
>     32    f32_typecast = ms.dataset.transforms.TypeCast(ms.float32)
>     33    # 将读取的 RGB 转为 GRAY 模式
>     34    convert_gray = ms.dataset.vision.ConvertColor(ms.dataset.vision.ConvertMode.COLOR_RGB2GRAY)
>     35
>     36    transform = [convert_gray, resize_op, f32_typecast, rescale_transform, CV.HWC2CHW()]
>     37    train_data = ds.ImageFolderDataset(dataset_dir=data_path, decode=True, extensions=[".JPEG", ".PNG", ".JPG"])
>     38    train_data = train_data.map(input_columns="image", operations= transform)
>     39    train_data = train_data.map(operations=lambda image: add_channels(image), \
>     40                                    input_columns=["image"], \
>     41                                    output_columns=["image"])
>     42    train_data = train_data.batch(batch_size=batch_size)
>     43    data_iter = train_data.create_dict_iterator()
>     44    print("数据集加载完成")
>     45
>     46    my_model = CNN()
>     47    loss = nn.CrossEntropyLoss(reduction='mean')
>     48    optimizer = nn.Adam(my_model.trainable_params(), learning_rate=0.01)
>     49    cnn_model = ms.Model(my_model, loss_fn=loss, optimizer=optimizer, metrics={'Accuracy': nn.Accuracy()})
>     50    print("网络构建完成")
>     51    num_epoch = 10
>     52    cnn_model.train(num_epoch, train_data,callbacks=[LossMonitor()],dataset_sink_mode=False)
>     53    inputs = ms.Tensor(np.random.randn(1, 1, 128, 128).astype(np.float32))
>     54    ms.save_checkpoint(my_model, 'cnn.ckpt')
>     55    export(my_model, inputs, file_name='cnn', file_format="MINDIR")

### 3. 重新组网

> 在前面章节中，我们已经完成了AI的模型的训练和导出。 需要重新构建成一个新的网络结构，可以使用MindSpore框架的 `nn.GraphCell()` 接口来实现。 该部分内容包括加载训练好的cnn模型，灰度化处理，重新构建网络结构，并导出新的模型。 重新组网、导出模型以及测试的代码如下：

>      1import cv2
>      2import mindspore as ms
>      3from mindspore.train.serialization import export
>      4import numpy as np
>      5from mindspore import nn, ops, context
>      6from CNN import export_cnn
>      7from GRAY import export_gray
>      8from Connection import export_connection
>      9
>     10class_names = ['BRDM_2', 'BTR_60', 'SLICY', 'T62', 'ZSU_23_4']
>     11class_labels = ["装甲侦察车", "装甲运输车", "不明", "坦克", "自行高炮"]
>     12class GrayCNN(nn.Cell):
>     13    def __init__(self):
>     14        super(GrayCNN, self).__init__()
>     15        self.split = ops.Split(axis=2, output_num=3)
>     16        self.conv1 = nn.Conv2d(1, 32, kernel_size=3, has_bias=True)
>     17        self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)
>     18        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, has_bias=True)
>     19        self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)
>     20        self.conv3 = nn.Conv2d(64, 128, kernel_size=3, has_bias=True)
>     21        self.pool3 = nn.MaxPool2d(kernel_size=2, stride=2)
>     22        self.flatten = nn.Flatten()
>     23        self.dense1 = nn.Dense(32768, 64)
>     24        self.dense2 = nn.Dense(64, 5)
>     25        self.relu = nn.ReLU()
>     26        self.cnn = nn.GraphCell(ms.load(file_name="cnn.mindir"))
>     27    def construct(self, x):
>     28        x = self.split(x)
>     29        b, g, r = x
>     30        x = (
>     31            b * 0.114 +
>     32            g * 0.587 +
>     33            r * 0.299
>     34        ).squeeze(-1)
>     35        x = ops.clip_by_value(x, 0, 255)
>     36        x = x / 255.0   # 数据归一化
>     37        x = x.reshape(1, 1, 128, 128)   # 灰度化处理后结果需要重塑成cnn输入形状
>     38        x = self.cnn(x)
>     39        return x
>     40
>     41if __name__ == "__main__":
>     42    export_cnn()
>     43    context.set_context(mode=context.GRAPH_MODE)
>     44    # 1. 读取图片（保持原始uint8类型）
>     45    color_image = cv2.imread("image_origin.jpg")  # 默认uint8
>     46    resized_image = cv2.resize(
>     47        color_image,
>     48        (128, 128),  # 目标尺寸（width, height）
>     49        interpolation=cv2.INTER_LINEAR
>     50    )
>     51    resized_image = resized_image.astype(np.float32)
>     52    resized_image_tensor = ms.Tensor(resized_image)
>     53
>     54    my_model = GrayCNN()
>     55    param_dict = ms.load_checkpoint("cnn.ckpt")
>     56    param_not_load, _ = ms.load_param_into_net(my_model, param_dict, strict_load=True)
>     57
>     58    input_tensor = ms.Tensor(np.ones((128, 128, 3), dtype=np.float32))
>     59    export(my_model, input_tensor, file_name='gray_cnn', file_format='MINDIR')
>     60
>     61    reload_cnn = nn.GraphCell(ms.load(file_name='gray_cnn.mindir'))
>     62    reload_cnn = ms.Model(reload_cnn)
>     63    m = reload_cnn.predict(resized_image_tensor)
>     64    print(m)
>     65    output_np = m.asnumpy()
>     66    if len(output_np.shape) == 2:  # 对于分类任务，通常输出是[batch_size, num_classes]
>     67        output_prob = np.exp(output_np) / np.exp(output_np).sum(axis=1, keepdims=True)
>     68        predicted_class = np.argmax(output_prob, axis=1)
>     69        print(f"预测结果: {class_labels[predicted_class[0]]}")

> 该代码首先定义了一个新的网络结构GrayCNN， 在 `construct` 方法中，首先通过gray模型处理输入图像，然后通过归一化和重塑张量，最后cnn模型进行识别。 在主函数中，首先导出cnn模型。 然后加载cnn模型参数，并使用MindSpore的 `export` 方法导出新的网络结构。 最后，使用重新组网后的模型进行预测，并输出结果。

### 4. 转换模型

> 在前面的代码中，我们已经使用了 `export` 方法导出了gray_cnn.mindir模型。 要将该模型部署到FT78NE平台，需要将MINDIR格式转换成mindspore lite的ms格式模型。 要将该模型转换为ms格式，可以使用MindSpore的转换工具(converter_lite)。 该工具已经集成在我们的 `YHFT-IDE` 中，可以直接在IDE中使用。或者也可以使用命令行工具进行转换。

> 转换命令如下：

>     ./converter_lite --fmk=MINDIR --modelFile=gray_cnn.mindir --outputFile=gray_cnn

> 转换完成后，会在当前目录下生成gray_cnn.ms模型文件。该模型可以使用可视化工具(netron)可以打开该文件，查看模型结构。 该工具可以直接在 `YHFT-IDE` 中使用，可以从官网下载使用，也可以在线使用。在线地址为：https://netron.app/。

> 该模型文件可视化如图所示：

### 5. 部署和运行程序

> - 在IDE中新建一个Python项目，将下面的 python完整代码示例 代码拷贝到项目中，并运行。 运行成功后，会在当前目录下生成一个名为 `gray_cnn.midir` 的模型文件，以及输出图片的预测结果。 结果如图所示：

>   将该文件转换为ms格式，并将ms格式模型和测试图片拷贝到FT78NE平台中。

> - 打开 `YHFT-IDE` ，新建工程。输入工程名、路径，工程类型选择 `Heterogeneous` ， 输入交叉编译工具路径，然后点确定。会生成一个异构模板工程。通过修改 `data_handler.cc` 文件中的函数来调整输入输出数据， 输入改成读取的图片路径；输出改成相应的后处理。在 `main` 函数设置运行后端；修改 `CMakeLists.txt` 文件， 添加openCV库的lib和include路径， 最后编译该工程，编译成功后将build文件夹下的 `main` 拷贝到FT78NE平台中， 要和gray_cnn.ms模型同一个文件夹下。

>   输入的c++代码示例如下：

>      1#include <opencv2/opencv.hpp>
>      2int readImage(float *reslut, std::string imagePath) {
>      3    cv::Mat color_image = cv::imread(imagePath.c_str(), cv::IMREAD_COLOR);
>      4    if (color_image.empty()) {
>      5        fprintf(stderr, "read image failed\n");
>      6        return -1;
>      7    }
>      8
>      9    int width = 128;
>     10    int height = 128;
>     11    cv::Mat resized_image;
>     12    cv::resize(color_image, resized_image, cv::Size(width, height), 0, 0, cv::INTER_LINEAR);
>     13    resized_image.convertTo(resized_image, CV_32F);
>     14    const int channels = resized_image.channels();
>     15    const int element_count = width * height * channels;
>     16
>     17    for (int i = 0; i < height; ++i) {
>     18        const float *src_ptr = resized_image.ptr<float>(i);
>     19        float *dst_ptr = reslut + i * width * channels;
>     20        std::memcpy(dst_ptr, src_ptr, width * channels * sizeof(float));
>     21    }
>     22    return 0;
>     23}

> 设置后端代码如下：

>     1auto context = std::make_shared<mindspore::Context>();
>     2auto &device_list = context->MutableDeviceInfo();
>     3context->SetBuiltInDelegate(mindspore::DelegateMode::kPNNA);
>     4auto cpu_info = std::make_shared<mindspore::CPUDeviceInfo>();
>     5auto dsp_info = std::make_shared<mindspore::FT78NEDeviceInfo>();
>     6device_list.push_back(dsp_info);
>     7device_list.push_back(cpu_info);

> 输出结果后处理代码如下：

>      1std::vector<float> floatArrayToVector(const float *array, size_t size) {
>      2    std::vector<float> vec(array, array + size);
>      3    return vec;
>      4}
>      5
>      6std::vector<float> numpy_exp(const std::vector<float> &x) {
>      7    std::vector<float> result;
>      8    result.reserve(x.size());
>      9    for (float num : x) {
>     10        result.push_back(exp(num));
>     11    }
>     12    return result;
>     13}
>     14
>     15float sum_rows(const std::vector<float> &vec, int axis) {
>     16    float sum = std::accumulate(vec.begin(), vec.end(), 0.0);
>     17    return sum;
>     18}
>     19
>     20std::vector<std::vector<float>> convertTo2D(const std::vector<float> &vec, int rows) {
>     21    std::vector<std::vector<float>> result;
>     22    if (vec.empty() || rows <= 0) {
>     23        return result;
>     24    }
>     25    int cols = (vec.size() + rows - 1) / rows;  // 计算列数
>     26    for (int i = 0; i < rows; ++i) {
>     27        std::vector<float> row;
>     28        for (int j = 0; j < cols; ++j) {
>     29        size_t index = i * cols + j;  // 计算索引
>     30        if (index < vec.size()) {
>     31            row.push_back(vec[index]);
>     32        } else {
>     33            row.push_back(0);  // 填充剩余空间，或者你可以选择不填充
>     34        }
>     35        }
>     36        result.push_back(row);
>     37    }
>     38    return result;
>     39}
>     40
>     41void GetOutputData(std::vector<MSTensor> &outputs) {
>     42    for (auto tensor : outputs) {
>     43        std::cout << "tensor name is:" << tensor.Name() << " tensor size is:" << tensor.DataSize()
>     44                << " tensor elements num is:" << tensor.ElementNum() << std::endl;
>     45        auto out_data = reinterpret_cast<const float *>(tensor.Data().get());
>     46        std::cout << "output data is:";
>     47        for (int i = 0; i < tensor.ElementNum() && i <= 50; i++) {
>     48        std::cout << out_data[i] << " ";
>     49        }
>     50        std::cout << std::endl;
>     51        std::vector<float> out = floatArrayToVector(out_data, tensor.ElementNum());
>     52        std::vector<float> exp_x = numpy_exp(out);
>     53        float sums = sum_rows(exp_x, 1);
>     54        std::vector<float> x1;
>     55        for (float num : exp_x) {
>     56            x1.push_back(num / sums);
>     57        }
>     58        std::vector<std::vector<float>> twoD = convertTo2D(x1, 1);
>     59        std::vector<int> argmax_indices = argmax(twoD);
>     60        std::vector<std::string> Predicted_class = {"装甲侦察车", "装甲运输车", "不明", "坦克", "自行高炮"};
>     61        std::cout << "预测结果: " << Predicted_class[argmax_indices[0]] << std::endl;
>     62    }
>     63}

> - 在FT78NE上运行可执行文件 `main` ，观察输出结果。与预期结果对比，验证模型推理的正确性。 执行命令如下：

>       1./main gray_cnn.ms image_origin.jpg

> 执行结果如下图：

## python完整代码示例

>      1# CNN.py
>      2import mindspore as ms
>      3from mindspore.train.serialization import export
>      4from mindspore import nn, context
>      5import numpy as np
>      6from PIL import Image
>      7import mindspore.dataset as ds
>      8from mindspore.dataset import py_transforms
>      9import mindspore.dataset.vision as CV
>     10from mindspore.train.callback import  LossMonitor
>     11
>     12# # Define directories and parameters
>     13batch_size = 32
>     14img_size = (128, 128)
>     15data_path = '../cnn-sar/Target'
>     16
>     17# 数据预处理
>     18def rescale_to_0_1(image):
>     19    return image / 255.0
>     20
>     21# 自定义函数，添加 color 通道维度
>     22def add_channels(image):
>     23    if len(image.shape) == 2:  # 单个图像，没有 cin_channel 维度
>     24        # print("before ===> ", image.shape)
>     25        # 添加 color 通道维度，(128x128) => (1, 128, 128)
>     26        image_four_channels = np.expand_dims(image, axis=0)
>     27        # print("after  ===> ", image_four_channels.shape)
>     28    else:
>     29        pass
>     30        return image
>     31    return image_four_channels
>     32
>     33class CNN(ms.nn.Cell):
>     34    def __init__(self):
>     35        super().__init__()
>     36        self.conv1 = nn.Conv2d(1, 32, kernel_size=3, has_bias=True)
>     37        self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)
>     38        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, has_bias=True)
>     39        self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)
>     40        self.conv3 = nn.Conv2d(64, 128, kernel_size=3, has_bias=True)
>     41        self.pool3 = nn.MaxPool2d(kernel_size=2, stride=2)
>     42        self.flatten = nn.Flatten()
>     43        self.dense1 = nn.Dense(32768, 64)
>     44        self.dense2 = nn.Dense(64, 5)
>     45        self.relu = nn.ReLU()
>     46
>     47    def construct(self, x):
>     48        x = self.relu(self.conv1(x))
>     49        x = self.pool1(x)
>     50        x = self.relu(self.conv2(x))
>     51        x = self.pool2(x)
>     52        x = self.relu(self.conv3(x))
>     53        x = self.pool3(x)
>     54        x = self.flatten(x)
>     55        x = self.relu(self.dense1(x))
>     56        x = self.dense2(x)
>     57        return x
>     58
>     59def export_cnn():
>     60    context.set_context(mode=context.PYNATIVE_MODE, device_target="CPU")
>     61    resize_op = CV.Resize(img_size)
>     62    rescale_transform = ms.dataset.transforms.Compose([rescale_to_0_1])
>     63    f32_typecast = ms.dataset.transforms.TypeCast(ms.float32)
>     64    # 将读取的 RGB 转为 GRAY 模式
>     65    convert_gray = ms.dataset.vision.ConvertColor(ms.dataset.vision.ConvertMode.COLOR_RGB2GRAY)
>     66
>     67    transform = [convert_gray, resize_op, f32_typecast, rescale_transform, CV.HWC2CHW()]
>     68    train_data = ds.ImageFolderDataset(dataset_dir=data_path, decode=True, extensions=[".JPEG", ".PNG", ".JPG"])
>     69    train_data = train_data.map(input_columns="image", operations= transform)
>     70    train_data = train_data.map(operations=lambda image: add_channels(image), \
>     71                                    input_columns=["image"], \
>     72                                    output_columns=["image"])
>     73    train_data = train_data.batch(batch_size=batch_size)
>     74    data_iter = train_data.create_dict_iterator()
>     75    print("数据集加载完成")
>     76    my_model = CNN()
>     77    loss = nn.CrossEntropyLoss(reduction='mean')
>     78    optimizer = nn.Adam(my_model.trainable_params(), learning_rate=0.01)
>     79    cnn_model = ms.Model(my_model, loss_fn=loss, optimizer=optimizer, metrics={'Accuracy': nn.Accuracy()})
>     80    print("网络构建完成")
>     81    num_epoch = 10
>     82    cnn_model.train(num_epoch, train_data,callbacks=[LossMonitor()],dataset_sink_mode=False)
>     83    inputs = ms.Tensor(np.random.randn(1, 1, 128, 128).astype(np.float32))
>     84    ms.save_checkpoint(my_model, 'cnn.ckpt')
>     85    export(my_model, inputs, file_name='cnn', file_format="MINDIR")

>      1# GRAY_CNN.py
>      2import cv2
>      3import mindspore as ms
>      4from mindspore.train.serialization import export
>      5import numpy as np
>      6from mindspore import nn, ops, context
>      7from CNN import export_cnn
>      8
>      9class_names = ['BRDM_2', 'BTR_60', 'SLICY', 'T62', 'ZSU_23_4']
>     10class_labels = ["装甲侦察车", "装甲运输车", "不明", "坦克", "自行高炮"]
>     11
>     12class GrayCNN(nn.Cell):
>     13    def __init__(self):
>     14        super(GrayCNN, self).__init__()
>     15        self.split = ops.Split(axis=2, output_num=3)
>     16        self.conv1 = nn.Conv2d(1, 32, kernel_size=3, has_bias=True)
>     17        self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)
>     18        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, has_bias=True)
>     19        self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)
>     20        self.conv3 = nn.Conv2d(64, 128, kernel_size=3, has_bias=True)
>     21        self.pool3 = nn.MaxPool2d(kernel_size=2, stride=2)
>     22        self.flatten = nn.Flatten()
>     23        self.dense1 = nn.Dense(32768, 64)
>     24        self.dense2 = nn.Dense(64, 5)
>     25        self.relu = nn.ReLU()
>     26        self.cnn = nn.GraphCell(ms.load(file_name="cnn.mindir"))
>     27
>     28    def construct(self, x):
>     29        x = self.split(x)
>     30        b, g, r = x
>     31        x = (
>     32            b * 0.114 +
>     33            g * 0.587 +
>     34            r * 0.299
>     35        ).squeeze(-1)
>     36        x = ops.clip_by_value(x, 0, 255)
>     37        x = x / 255.0   # 数据归一化
>     38        x = x.reshape(1, 1, 128, 128)   # 灰度化处理后结果需要重塑成cnn输入形状
>     39        x = self.cnn(x)
>     40        return x
>     41
>     42if __name__ == "__main__":
>     43    export_cnn()
>     44    context.set_context(mode=context.GRAPH_MODE)
>     45    # 1. 读取图片（保持原始uint8类型）
>     46    color_image = cv2.imread("image_origin.jpg")  # 默认uint8
>     47    resized_image = cv2.resize(
>     48        color_image,
>     49        (128, 128),  # 目标尺寸（width, height）
>     50        interpolation=cv2.INTER_LINEAR
>     51    )
>     52    resized_image = resized_image.astype(np.float32)
>     53    resized_image_tensor = ms.Tensor(resized_image)
>     54    my_model = GrayCNN()
>     55    param_dict = ms.load_checkpoint("cnn.ckpt")
>     56    param_not_load, _ = ms.load_param_into_net(my_model, param_dict, strict_load=True)
>     57    input_tensor = ms.Tensor(np.ones((128, 128, 3), dtype=np.float32))
>     58    export(my_model, input_tensor, file_name='gray_cnn', file_format='MINDIR')
>     59    reload_cnn = nn.GraphCell(ms.load(file_name='gray_cnn.mindir'))
>     60    reload_cnn = ms.Model(reload_cnn)
>     61    m = reload_cnn.predict(resized_image_tensor)
>     62    print(m)
>     63    output_np = m.asnumpy()
>     64    if len(output_np.shape) == 2:  # 对于分类任务，通常输出是[batch_size, num_classes]
>     65        output_prob = np.exp(output_np) / np.exp(output_np).sum(axis=1, keepdims=True)
>     66        predicted_class = np.argmax(output_prob, axis=1)
>     67        print(f"预测结果: {class_labels[predicted_class[0]]}")