上篇文章,使用MobileNet模型进行了物品分类测试,通过USB摄像头获取实时画面,进行多种物品的实时分类。
本篇来介绍了具体代码实现。
MobileNet模型是基于深度可分离卷积,自2017年由谷歌公司提出,MobileNet可谓是轻量级网络中的Inception,适用于移动或嵌入式设备中,MobileNet模型的发展经历了从V1到V3。
MobileNet的基本单元是深度级可分离卷积(depthwise separable convolution),这种结构之前已经被使用在Inception模型中。
深度级可分离卷积是一种可分解卷积操作,可分解为两个更小的操作:
对于depthwise separable convolution,其首先是采用depthwise convolution对不同输入通道分别进行卷积,然后采用pointwise convolution将上面的输出再进行结合,这样其实整体效果和一个标准卷积是差不多的,但是会大大减少计算量和模型参数量。
MobileNet基本原理了解一下就好,我们主要是要能使用训练好的模型实现物品分类功能。
先来看下整个代码的项目结构,然后再来分别介绍各个功能模块。
本篇物品分类的代码,与之前使用SSD模型进行AI物品检测的代码结构相同,主要区别在识别的业务流程。
主要的业务流程包括:
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;
}
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;
}
板子资料里的物品分类例程,只显示了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 memory
while ((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);
}
和之前使用SSD模型的代码工程类似,修改好qt的pro和pri文件后,使用编译脚本进行交叉编译。
编译完成后,程序放到板子中进行测试,运行前确认板子中的MobileNet模型文件和物品标签文件,如下图:
运行时,板子需要外接USB摄像头,以及外接HDMI显示器:
运行效果的演示视频可见上篇文章
本篇介绍了在飞凌OK3568-C开发板中,外接USB摄像头,利用Qt和MobileNet进行AI物品识别分类,通过已训练好的MobileNet_V1模型,进行摄像头画面的实时AI物品分类的代码实现原理
更多回帖