飞凌嵌入式
直播中

fsdzdzy

10年用户 260经验值
擅长:嵌入式技术 控制/MCU opencv
私信 关注
[技术]

【飞凌RK3568开发板试用体验】12-USB摄像头AI物品识别分类代码实现

上篇文章,使用MobileNet模型进行了物品分类测试,通过USB摄像头获取实时画面,进行多种物品的实时分类。

本篇来介绍了具体代码实现。

1MobileNet模型介绍

MobileNet模型是基于深度可分离卷积,自2017年由谷歌公司提出,MobileNet可谓是轻量级网络中的Inception,适用于移动或嵌入式设备中,MobileNet模型的发展经历了从V1到V3。

MobileNet的基本单元是深度级可分离卷积(depthwise separable convolution),这种结构之前已经被使用在Inception模型中。

深度级可分离卷积是一种可分解卷积操作,可分解为两个更小的操作:

  • depthwise convolution,针对每个输入通道采用不同的卷积核,一个卷积核对应一个输入通道,所以它是depth级别的操作
  • pointwise convolution,就是普通的卷积,只不过其采用1x1的卷积核

对于depthwise separable convolution,其首先是采用depthwise convolution对不同输入通道分别进行卷积,然后采用pointwise convolution将上面的输出再进行结合,这样其实整体效果和一个标准卷积是差不多的,但是会大大减少计算量和模型参数量。

MobileNet基本原理了解一下就好,我们主要是要能使用训练好的模型实现物品分类功能。

2 USB摄像头AI物品识别分类代码

先来看下整个代码的项目结构,然后再来分别介绍各个功能模块。

  • imageutil.h:图像类型转换相关函数
  • myvideosourceface.cpp/h:用于USB摄像头图像显示
  • qtcamera.cpp/h:qt界面
  • rknn_moblienet_process.cpp/h:用于MobileNet模型进行AI物品识别分类的接口函数

3 与物品检测的不同之处

本篇物品分类的代码,与之前使用SSD模型进行AI物品检测的代码结构相同,主要区别在识别的业务流程。

3.1 物品识别分类业务流程

主要的业务流程包括:

  • 设置输入图像
  • 运行rknn模型
  • 获取输出结果
  • 将结果显示在图像上(Top5的物品信息)
int RknnMobilenetModel::DoRknnMobilenet(cv::Mat &src, cv::Mat &res)
{
    const int img_width = 224;
    const int img_height = 224;
    const int img_channels = 3;
    int ret = 0;
​
    cv::Mat img = src.clone();
    if (src.cols != img_width || src.rows != img_height)
    {
        cv::resize(img, img, cv::Size(img_width, img_height), (0, 0), (0, 0), cv::INTER_LINEAR);
    }
​
    // Set Input Data
    rknn_input inputs[1];
    memset(inputs, 0, sizeof(inputs));
    inputs[0].index = 0;
    inputs[0].type = RKNN_TENSOR_UINT8;
    inputs[0].size = img.cols * img.rows * img.channels();
    inputs[0].fmt = RKNN_TENSOR_NHWC;
    inputs[0].buf = img.data;
​
    ret = rknn_inputs_set(m_rknnCtx, m_rknnIoNum.n_input, inputs);
    if (ret < 0)
    {
        printf("rknn_input_set fail! ret=%d\n", ret);
        return -1;
    }
​
    // Run
    ret = rknn_run(m_rknnCtx, nullptr);
    if (ret < 0)
    {
        printf("rknn_run fail! ret=%d\n", ret);
        return -1;
    }
​
    // Get Output
    memset(m_outputs, 0, sizeof(m_outputs));
    m_outputs[0].want_float = 1;
    ret = rknn_outputs_get(m_rknnCtx, 1, m_outputs, NULL);
    if (ret < 0)
    {
        printf("rknn_outputs_get fail! ret=%d\n", ret);
        return -1;
    }
​
    // Post Process
    for (int i = 0; i < m_rknnIoNum.n_output; i++)
    {
        uint32_t MaxClass[5];
        float fMaxProb[5];
        float *buffer = (float *)m_outputs[i].buf;
        uint32_t sz = m_outputs[i].size / 4;
​
        rknn_GetTop(buffer, fMaxProb, MaxClass, sz, 5);
​
        if (fMaxProb[0] > 0.5)
        {
            putText(src, "--- Top5 ---", Point(10, 30), 1, 1.5, Scalar(0, 255, 0, 255), 2);
            for (int i = 0; i < 5; i++)
            {
                char *label = m_labels[MaxClass[i]];
                char showinfo[1024] = {0};
                sprintf((char *)showinfo, "[%03d](%.2f): %s\0", MaxClass[i], fMaxProb[i], label);
                putText(src, showinfo, Point(10, 30+(i+1)*35), 1, 1.5, Scalar(0, 255, 0, 255), 2);
            }
        }
    }
    res = src;
​
    return 0;
}

3.2 Top5类型的获取

MobileNet模型识别物品,会对其相似度给出一个概率,这里显示相似度最高的5种:

#define MAX_TOP_NUM 20
static int rknn_GetTop(float *pfProb, float *pfMaxProb, uint32_t *pMaxClass, uint32_t outputCount, uint32_t topNum)
{
    uint32_t i, j;
​
    if (topNum > MAX_TOP_NUM)
        return 0;
​
    memset(pfMaxProb, 0, sizeof(float) * topNum);
    memset(pMaxClass, 0xff, sizeof(float) * topNum);
​
    for (j = 0; j < topNum; j++)
    {
        for (i = 0; i < outputCount; i++)
        {
            if ((i == *(pMaxClass + 0)) || (i == *(pMaxClass + 1)) || (i == *(pMaxClass + 2)) ||
                (i == *(pMaxClass + 3)) || (i == *(pMaxClass + 4)))
            {
                continue;
            }
​
            if (pfProb[i] > *(pfMaxProb + j))
            {
                *(pfMaxProb + j) = pfProb[i];
                *(pMaxClass + j) = i;
            }
        }
    }
​
    return 1;
}

3.3 物品名称的对应关系

板子资料里的物品分类例程,只显示了Top对应物品的序号,没有显示具体的物品名称,不方便直观的确认识别的是否正确。

为了能显示识别的物品名称,在网上找到了MobileNet模型对应的物品名称文件,包含有1000种物品,如下图所示:

此文件就是一个普通的文本文件,每一行对应一种物品类别,一共有1000行,对应识别结果的0~999

将此文件下载下来,放到板子中用于物品分类时显示对应的物品名称。

在程序中,通过loadLabelName函数来加载这个物品名称文件,该函数的具体实现如下:

//在初始化时, 对模型中物品对应的物品名称文件进行初始化
ret = loadLabelName(LABEL_NALE_TXT_PATH, m_labels);
​
char *readLine(FILE *fp, char *buffer, int *len)
{
    int ch;
    int i = 0;
    size_t buff_len = 0;
​
    buffer = (char *)malloc(buff_len + 1);
    if (!buffer)
        return NULL; // Out of memorywhile ((ch = fgetc(fp)) != '\n' && ch != EOF)
    {
        buff_len++;
        void *tmp = realloc(buffer, buff_len + 1);
        if (tmp == NULL)
        {
            free(buffer);
            return NULL; // Out of memory
        }
        buffer = (char *)tmp;
​
        buffer[i] = (char)ch;
        i++;
    }
    buffer[i] = '\0';
​
    *len = buff_len;
​
    // Detect end
    if (ch == EOF && (i == 0 || ferror(fp)))
    {
        free(buffer);
        return NULL;
    }
    return buffer;
}
​
int readLines(const char *fileName, char *lines[], int max_line)
{
    FILE *file = fopen(fileName, "r");
    char *s;
    int i = 0;
    int n = 0;
​
    if (file == NULL)
    {
        printf("Open %s fail!\n", fileName);
        return -1;
    }
​
    while ((s = readLine(file, s, &n)) != NULL)
    {
        lines[i++] = s;
        if (i >= max_line)
            break;
    }
    return i;
}
​
int loadLabelName(const char *locationFilename, char *label[])
{
    printf("ssd - loadLabelName %s\n", locationFilename);
    return readLines(locationFilename, label, NUM_CLASS);
}

4 交叉编译与测试

和之前使用SSD模型的代码工程类似,修改好qt的pro和pri文件后,使用编译脚本进行交叉编译。

编译完成后,程序放到板子中进行测试,运行前确认板子中的MobileNet模型文件和物品标签文件,如下图:
4.png

运行时,板子需要外接USB摄像头,以及外接HDMI显示器:

运行效果的演示视频可见上篇文章

5 总结

本篇介绍了在飞凌OK3568-C开发板中,外接USB摄像头,利用Qt和MobileNet进行AI物品识别分类,通过已训练好的MobileNet_V1模型,进行摄像头画面的实时AI物品分类的代码实现原理
6.png

回帖(1)

更多回帖

发帖
×
20
完善资料,
赚取积分