物体识别本质上是分类问题,相对于传统分类算法,基于CNN算法实现的分类网络,可以实现较高准确率。由于该CNN网络需要运行在嵌入式设备上,需要选择一款适合于嵌入式设备的网络,在保证准确率的前提下,提升网络运行速度,通过对比网络各项性能参数,最终本文选择采用SqeezeNet,虽然该网络的参数很少但是计算量却较大,需要对该网络进行改进,减少运算量,提升运算速度。
整个过程分为两个步骤:图像预处理和神经网络推理。
需要提前安装Tengine框架,参考:https://blog.csdn.net/Bluesyxx/article/details/85255634
代码地址为:https://github.com/BluesYu/MarStech_Vision_Sensor/tree/master/sqz_mode
一,基础知识
SqueezeNet网络结构通过使用1*1等小型卷积核代替原本的大卷积核、减小池化操作等,借助内部特殊的网络结构,实现参数的减少。同时,该网络使用Fire 模块这一特殊结构,实现参数压缩。
Fire 模块(即Fire Module)是本算法最重要的结构,它的构造原理是:将单层的卷积(即conv层)转化为Squeeze层和Expand层,并且这两层都各自带上激活层(即Relu层),具体结构如下。
该网络结构设计理念与VGG网络类似,具体结构结构如图3.39所示,它采用卷积操作层层堆叠的方式,但是该网络使用的堆叠采用的是前述的Fire module,一共设置了9层的fire module,中间添加池化层(Max pooling),最后使用Global avg pooling层代替了全连接(FC)层,使网络参数大大减小。
基于ImageNet的数据测试,对比网络的准确率和速度,参数如下:
网络 | 压缩方法 | 数据 类型 | 原模型->压缩模型尺寸 | 减小尺寸 vs.AlexNet | Top-1 准确率 | Top-5 准确率 |
AlexNet | None (baseline) | 32 bit | 240MB | 1x | 57.2% | 80.3% |
AlexNet | SVD(Denton et al.2014) | 32 bit | 240MB->48MB | 5x | 56% | 79.4% |
AlexNet | Network Pruning (Han et al.,2015b) | 32 bit | 240MB->27MB | 9x | 57.2% | 80.3% |
AlexNet | Deep Compression (Han et al.2015a) | 5-8bit | 240MB->7MB | 35x | 57.2% | 80.3% |
SqueezeNet | None | 32 bit | 4.8MB | 50x | 57.5% | 80.3% |
SqueezeNet | Deep Compression | 8 bit | 4.8MB->0.66MB | 363x | 57.5% | 80.3% |
SqueezeNet | Deep Compression | 6 bit | 4.8MB->0.47MB | 510x | 57.5% | 80.3% |
SqueezeNet在ImageNet数据集训练千分类模型,准确率略微高于AlexNet网络,但是模型的尺寸却很小,通过深压缩技术,模型可以缩小510倍,同时在千分类上Top-5的识别准确率可以达到80%以上,性能良好。
网络层次 | 输出规则 | 滤波器尺寸 /步长 | 深度 | S(1*1) | e(1*1) | e(3*3) | 参数个数 |
1卷积层 | 111x111x96 | 7x7/2 (x96) | 1 |
|
|
| 14208 |
最大池化层1 | 55x55x96 | 3x3/2 | 0 |
|
|
|
|
2 Fire层 | 55x55x128 |
| 2 | 16 | 64 | 64 | 11920 |
3 Fire层 | 55x55x128 |
| 2 | 16 | 64 | 64 | 12432 |
4 Fire层 | 55x55x256 |
| 2 | 32 | 128 | 128 | 45344 |
最大池化层2 | 27x27x256 | 3x3/2 | 0 |
|
|
|
|
5 Fire层 | 27x27x256 |
| 2 | 32 | 128 | 128 | 49440 |
6 Fire层 | 27x27x384 |
| 2 | 48 | 192 | 192 | 104880 |
7 Fire层 | 27x27x384 |
| 2 | 48 | 192 | 192 | 111024 |
8 Fire层 | 27x27x512 |
| 2 | 64 | 256 | 256 | 188992 |
最大池化层3 | 13x13x512 | 3x3/2 | 0 |
|
|
|
|
9 Fire层 | 13x13x512 |
| 2 | 64 | 256 | 256 | 197184 |
10卷积层 | 13x13x1000 | 1x1/1 (1000) | 1 |
|
|
| 513000 |
全局平均 池化层 | 1x1x1000 | 13x13/1 | 0 |
|
|
| 总参数: 1248424 |
二,模型实现过程:
主要相关的工程为:common_util.hpp、image_process.hpp、sqz_mode.cpp和sqz_mode.h。
其中,SqueezeNet模型在文件夹中sqz_net中,包含文件imagenet_slim_labels.txt、squeezenet_v1.1.caffemodel、sqz.prototxt和synset_words.txt。
1,设置文件路径
const char * sqz_text_file="./sqz_net/sqz.prototxt"; //里面定义了模型的网络结构
const char * sqz_model_file="./sqz_net/squeezenet_v1.1.caffemodel"; //网络模型
const char * sqz_image_file="./sqz_net/cat.jpg";//原始图片
const char * sqz_label_file="./sqz_net/synset_words.txt"; //ImageNet标签文件,存放分类名称
const float sqz_channel_mean[3]={104.007, 116.669, 122.679};
2,导入模型数据、推理:
/*******************************************************************
* 名称: LoadLabelFile_sqz
* 功能: 导入图像模型
* 入口参数: vector< string> &result: const char *fname:
* 出口参数: 空
*******************************************************************/
void LoadLabelFile_sqz( vector< string> &result, const char *fname)//导入图片标记
{
ifstream labels(fname);
string line;
while ( getline(labels, line))
result.push_back(line);
}
/*******************************************************************
* 名称: PrintTopLabels_sqz
* 功能: 打印标记函数(千分类)
* 入口参数: const char *sqz_label_file:文件参数 float *data:输入的数据
string &name_labels:名称数组 int &rate_num:比率,保留三位小数
* 出口参数: 空
*******************************************************************/
void PrintTopLabels_sqz(const char *sqz_label_file, float *data,string &name_labels,int &rate_num)//打印图片标记
{
// load labels
vector< string> labels;
LoadLabelFile_sqz(labels, sqz_label_file);
float *end = data + 1000;
vector<float> result(data, end);
vector<int> top_N = Argmax(result, 5);
//int fd = UART_Init();
//char name_temp[128];
int len_buff=labels.size();
if(len_buff>0)
for (unsigned int i = 0; i < top_N.size(); i++)
{
int idx = top_N[i];
// setiosflags 是包含在命名空间iomanip 中的C++ 操作符,该操作符的作用是执行由有参数指定区域内的动作;
// iso::fixed 是操作符setiosflags 的参数之一,该参数指定的动作是以带小数点的形式表示浮点数,并且在允许的精度范围内尽可能的把数字移向小数点右侧;
if(result[idx]>0.3)
{
cout << fixed << setprecision(4) << result[idx] << " - \"" << labels[idx] << "\"\n";
name_labels = labels[idx].c_str();
rate_num = (int)(result[idx]*1000);
// strcpy(name_temp,labels.c_str());
// color_temp[len_buff]='\n';
// len=UART_Send(fd,(unsigned char*)color_temp,len_buff+1);//发送数
}
}
}
/*******************************************************************
* 名称: get_input_data_sqz
* 功能: 获取输入数据
* 入口参数:const char* sqz_image_file:输入文件 float *input_data:输出
int img_h:图片高度 int img_w:图片宽度
const float* mean:RGB取值阈值 float scale:缩放因子
* 出口参数: 空
*******************************************************************/
// void get_input_data_sqz(const char* sqz_image_file, float *input_data, int img_h, int img_w,const float* mean, float scale)//获取输入数据 float scale:缩放
void get_input_data_sqz( Mat img, float *input_data, int img_h, int img_w,const float* mean, float scale)//获取输入数据 float scale:缩放
{
// Mat img = imread(sqz_image_file, -1);
if (img.empty())
{
cerr << "failed to read image file " << "\n";
return;
}
resize(img, img, Size(img_h, img_w));
img.convertTo(img, CV_32FC3); // 类型又UINT8变为了FLOAT32位
float *img_data = (float *)img.data;//图像数据
int hw = img_h * img_w;
for (int h = 0; h < img_h; h++)
for (int w = 0; w < img_w; w++)
for (int c = 0; c < 3; c++)
{
input_data[c * hw + h * img_w + w] = (*img_data - mean[c])*scale;//对应RGB阈值
img_data++;
}
}
3,按照Tengine框架,使用的方法如下:
int main(int argc, char * argv[])
{
int img_h=227;//归一化图像大小227*227
int img_w=227;
string model_name = "squeeze_net";//名称
/*******************************************************************
* 名称: init_tengine_library
* 功能: 初始化tengine库函数
* 入口参数:
* 出口参数: 0:成功 -1:失败
*******************************************************************/
int init_tengine=init_tengine_library();//对初始化
if(init_tengine==-1)
{
cout<<"init tengine failed\n";
return -1;
}
/*******************************************************************
* 名称: request_tengine_version
* 功能: 版本设置
* 入口参数:
* 出口参数: 0:不支持 1:支持
*******************************************************************/
if(request_tengine_version("0.1")<0)
return -2;
/*******************************************************************
* 名称: load_model
* 功能: 模型加载
* 入口参数:const char* model_name:指定模型名称
const char* model_format:“caffe”,“tensorflow”,“onnx”,“tengine”
const char* sqz_text_file:模型名称;
const char* sqz_model_file:模型文件;
* 出口参数: 0:成功 -1:失败
*******************************************************************/
if(load_model(model_name.c_str(),"caffe",sqz_text_file,sqz_model_file)<0)
return 1;
cout<<"Load model successfully\n";
/*******************************************************************
* 名称: create_runtime_graph
* 功能: 创建运行图
* 入口参数:const char* graph_name:指定图名称
const char* model_name:模型名称
workspace_t ws:工具空间句柄,传入NULL,使用默认空间
* 出口参数: graph_t:运行图句柄,失败返回NULL
*******************************************************************/
graph_t graph=create_runtime_graph("graph0",model_name.c_str(),NULL);
if(!check_graph_valid(graph))
{
cout<<"Create graph0 failed\n";
return 1;
}
/* get input tensor */
int node_idx = 0;
int tensor_idx = 0;
tensor_t input_tensor;//输入
tensor_t output_tensor;//输出
float * input_data;//输入数据规格
VideoCapture capture(0);//打开摄像头
Mat srcImage;
input_data=(float*) malloc (sizeof(float) * img_h *img_w *3);
/*******************************************************************
* 名称: get_graph_input_tensor
* 功能: 获取图的输入节点的张量句柄
* 入口参数:graph_t graph:运行图句柄
int node_idx:输入节点索引值
int tensor_idx:张量索引值
* 出口参数: tensor_t:张量句柄,失败返回NULL
*******************************************************************/
input_tensor=get_graph_input_tensor(graph , node_idx , tensor_idx );
if(!check_tensor_valid(input_tensor))
{
printf("Cannot find input tensor,node_idx: %d,tensor_idx: %d\n",node_idx,tensor_idx);
return -1;
}
int dims[]={1,3,img_h,img_w}; //设置维度数组
/*******************************************************************
* 名称: set_tensor_shape
* 功能: 设置张量形状
* 入口参数:tensor_t input_tensor:张量句柄
int dims:维度的数组
int dims_num: 维度个数 4维
* 出口参数: 无
*******************************************************************/
set_tensor_shape(input_tensor,dims,4);
/*******************************************************************
* 名称: get_tensor_shape
* 功能: 获取张量形状
* 入口参数:tensor_t input_tensor:张量句柄
int dims:维度的数组
int dims_num: 维度个数 4维
* 出口参数: 无
*******************************************************************/
int a=get_tensor_shape(input_tensor,dims,4);
if(a!=-1)
cout << "维度数目:" <<a<< endl;
while(1)
{
capture >> srcImage; //获取一帧图像
//Mat srcImage=imread("a.jpg");
if (!srcImage.data) //错误的信息处理
{
cout << "Could not open or find the image" << endl;
continue;
//getchar();
// return -1;
}
/* prepare input data */
get_input_data_sqz( srcImage, input_data, img_h, img_w, sqz_channel_mean,1);//获取输入
/* setup input buffer */
/*******************************************************************
* 名称: set_tensor_buffer
* 功能: 设置张量数据缓冲区
* 入口参数:tensor_t input_tensor:张量句柄
void *buffer:数据缓冲区指针(需要释放)
int size:缓冲区大小(4维)
* 出口参数: 0:成功 -1:失败
*******************************************************************/
if(set_tensor_buffer(input_tensor,input_data,3*img_h*img_w*4)<0)
{
printf("Set buffer for tensor failed\n");
return -1;
}
/* run the graph */
/*******************************************************************
* 名称: prerun_graph
* 功能: 图预运行,准备参数
* 入口参数: graph_t graph:运行图句柄
* 出口参数: 0:成功 -1:失败
*******************************************************************/
prerun_graph(graph);
/*******************************************************************
* 名称: run_graph
* 功能: 运行图
* 入口参数: graph_t graph:运行图句柄
int block(0:调用wait_graph等待运行结束; 1:阻塞运行)
* 出口参数: 0:成功 -1:失败
*******************************************************************/
run_graph(graph,1);
/*******************************************************************
* 名称: get_graph_output_tensor
* 功能: 获取图的输出节点的张量句柄
* 入口参数:graph_t graph:运行图句柄
int node_idx:输出节点索引值
int tensor_idx: 张量索引值
* 出口参数: tensor_t:张量句柄,失败返回NULL
*******************************************************************/
output_tensor = get_graph_output_tensor(graph, 0, 0);
/*******************************************************************
* 名称: get_tensor_buffer
* 功能: 获取张量数据缓冲区
* 入口参数: tensor_t:图像张量句柄
* 出口参数: void *:数据缓存区指针(NULL表示数据缓冲区未申请)
*******************************************************************/
float *data = (float *)get_tensor_buffer(output_tensor);
string name_labels = "0" ;
int rate_num = 0;
PrintTopLabels_sqz(sqz_label_file,data,name_labels,rate_num);//打印图片标记
int fd = UART_Init();
if(rate_num != 0)
{
//cout << rate_num<< " - \"" << name_labels<< "\"\n";
char sqz_temp[128];
unsigned char temp_array[8];
int len = 0;
int len_buff=name_labels.size();
int2char_num(rate_num,temp_array);
strcpy(sqz_temp,name_labels.c_str());
len=UART_Send(fd,(unsigned char*)sqz_temp,len_buff);//发送数据
for(int i = 5;i>=3;i--)
{
temp_array[i] = temp_array[i-3];
}
temp_array[0]='(';
temp_array[1]='0';
temp_array[2]='.';
temp_array[6]=')';
temp_array[7]='\n';
len=UART_Send(fd,(unsigned char*)temp_array,8);//发送数据
}
//PrintTopLabels_sqz(sqz_label_file, data);
}
put_graph_tensor(output_tensor);//释放输出张量句柄
put_graph_tensor(input_tensor);//释放输入张量句柄
postrun_graph(graph); //释放运行图资源
destroy_runtime_graph(graph);//销毁运行图
remove_model(model_name.c_str());//移除模型文件
cout<<"ALL TEST DONE\n";
release_tengine_library(); //资源释放操作
free(input_data);
input_data=NULL;//free释放堆空间后,必须把无效指针变为空
return 0;
}
4,编译文件
# ------------------------------------------FileInfo-----------------------------------------------------
# File name: CmksList.txt
# Created by: BluesYu
# Last modified Date: 2019-01-23
# Last Version: 1.0
# Descriptions: 模型编译所需设置
# ------------------------------------------------------------------------------------------------------
cmake_minimum_required (VERSION 2.8)
project(sqz_net)
set( TENGINE_DIR ~/Tengine)
#set( TENGINE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../ )
set( INSTALL_DIR ${TENGINE_DIR}/install/ )
set( TENGINE_LIBS tengine protobuf)
#flag
set(CMAKE_CFLAGS "-std=gnu89 -O3 -Wall")
set(CMAKE_CXX_FLAGS "-std=c++11 -O3 -Wall")
#opencv
find_package(OpenCV REQUIRED)
#include
include_directories(${INSTALL_DIR}/include)
include_directories(~/sqz_mode)
#lib
link_directories(${INSTALL_DIR}/lib)
find_package(OpenCV REQUIRED)
#exe
add_executable(sqz_test sqz_test.cpp sqz_mode.cpp uart_io/uart_io.cpp)
target_link_libraries(sqz_test ${TENGINE_LIBS} ${OpenCV_LIBS})
三,其他
注意: 1,调节焦距;
2,采集需要注意,尽量靠近物体;
3, 编译命令为:cmake . make -j4
具体视觉传感器测试、购买可以咨询:火星人俱乐部官网(https://www.imarsclub.com/web/index),电话或邮件联系即可。传感器已经申请专利,商业使用需要授权。
火星人视觉传感器完整项目介绍:https://blog.csdn.net/Bluesyxx/article/details/98474347
火星人视觉传感器是一个开放平台,相关电路版图、代码对外开放,可以自行下载,代码地址:https://github.com/BluesYu/MarStech_Vision_Sensor,欢迎star和fork,有问题可以再github上交流。
本项目为开源项目,不以盈利为目的,开源社区需要大家一起努力,欢迎大家一起来开发!