千分类模型嵌入式设备实现 —嵌入式AI设备--火星人视觉传感器

      物体识别本质上是分类问题,相对于传统分类算法,基于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%以上,性能良好。

网络层次

输出规则

滤波器尺寸

/步长

深度

S1*1

e1*1

e3*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.hppimage_process.hppsqz_mode.cppsqz_mode.h

其中,SqueezeNet模型在文件夹中sqz_net中,包含文件imagenet_slim_labels.txtsqueezenet_v1.1.caffemodelsqz.prototxtsynset_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上交流。

       本项目为开源项目,不以盈利为目的,开源社区需要大家一起努力,欢迎大家一起来开发!

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值