在微信小程序部署AI模型的几种方法

3 篇文章 0 订阅
2 篇文章 0 订阅

前言

本文只是分享思路,不提供可完整运行的项目代码

onnx部署

以目标检测类模型为例,该类模型会输出类别信息置信度包含检测框的4个坐标信息

但不是所有的onnx模型都能在微信小程序部署,有些算子不支持,这种情况需要点特殊操作,我暂时不能解决。

微信小程序提供的接口相当于使用onnxruntime的接口运行onnx模型,我们要做的就是将视频帧数据(包含RGBA的一维像素数组)转换成对应形状的数组(比如3*224*224的一维Float32Array)然后调用接口并将图像输入得到运行的结果(比如一个1*10*6的一维Float32Array,代表着10个预测框的类别,置信度和框的4个坐标)然后将结果处理(比如行人检测,给置信度设置一个阈值0.5,筛选置信度大于阈值的数组的index,然后按照index取出相应的类别和框坐标)最后在wxml中显示类别名或置信度或在canvas绘制框。

代码框架

这里采用的是实时帧数据,按预设频率调用一帧数据并后处理得到结果

onLoad主体

    onLoad(){
      // 创建相机上下文
      const context = wx.createCameraContext();
      // 定义实时帧回调函数
      this.listener=context.onCameraFrame((frame)=>this.CamFramCall(frame));
      // 初始化session
      this.initSession()
    },

相机实时帧回调函数 

得到的实时帧数据因系统而异,并不固定(这对后面画追踪框的时候不利)

我的处理方法是把帧数据和<camera>组件的长宽比例统一,这样得到坐标后再乘以一个比例系数即可映射到<camera>因为输进去模型的是帧数据,所以返回的追踪框坐标是基于帧数据的,直接画在<camera>上的canvas有可能出现框的位置有偏差

回调函数里的逻辑是设置<camera>的长(我把宽定死到手机屏幕长度的0.9)预处理图片数据进行推理关闭监听(至此完成一帧)

    CamFramCall(frame){
      // 根据实时帧的图片长宽比例设置<camera>组件展示大小
      this.setData({
        windowHeight:frame.height/frame.width*wx.getSystemInfoSync().windowWidth*0.9
      })
      var dstInput=new Float32Array(3*this.data.imgH*this.data.imgW).fill(255)
      // 调用图片预处理函数对实时帧数据进行处理
      this.preProcess(frame,dstInput)
      // 将处理完的数据进行推理得到结果
      this.infer(dstInput)
      console.log('完成一次帧循环')
      // 关闭监听
      this.listener.stop()
    },

初始化session

首先得将onnx上传至云端,获得一个存储路径(比如cloud://cloud1-8gcwcxqrb8722e9e.636c-cloud1-8gcwcxqrb8722e9e-1324077753/rtdetrWorker.onnx

当用户首次使用该小程序时,手机里没有onnx模型的存储,需要从云端下载;而已经非第一次使用该小程序的用户手机里已经保存了之前下载的onnx模型,就无需下载。所以此处代码逻辑是需要检测用户的存储里是否有该onnx模型,不存在就下载,下载完并保存模型文件后就执行下一步;存在就直接执行下一步

    initSession(){
      // onnx云端下载路径
      const cloudPath='cloud://cloud1-8gcwcxqrb8722e9e.636c-cloud1-8gcwcxqrb8722e9e-1324077753/best.onnx'
      const lastIndex=cloudPath.lastIndexOf('/')
      const filename=cloudPath.substring(lastIndex+1)
      const modelPath=`${wx.env.USER_DATA_PATH}/`+filename
      // 检测onnx文件是否存在
      wx.getFileSystemManager().access({
        path:modelPath,
        // 如果存在就创建session,定时开启监听实时帧
        success:(res)=>{
          console.log('file already exist')
          this.createInferenceSession(modelPath)
          setInterval(()=>{this.listener.start()},1000)
        },
        // 如果不存在
        fail:(res)=>{
          console.error(res)
          wx.cloud.init()
          console.log('begin download model')
          // 下载提示框
          wx.showLoading({
            title: '加载检测中',
          })
          // 调用自定义的下载文件函数
          this.downloadFile(cloudPath,function(r) {
            console.log(`下载进度:${r.progress}%,已下载${r.totalBytesWritten}B,共${r.totalBytesExpectedToWrite}B`)
          }).then(result=>{
            // 下载文件成功后保存
            wx.getFileSystemManager().saveFile({
              tempFilePath:result.tempFilePath,
              filePath:modelPath,
              // 保存文件成功后创建session,定时开启监听实时帧
              success:(res)=>{
                const modelPath=res.savedFilePath
                console.log('save onnx model at path:'+modelPath)
                this.createInferenceSession(modelPath)
                // 关闭下载提示框
                wx.hideLoading()
                setInterval(()=>{this.listener.start()},1000)
              },
              fail:(res)=>{
                console.error(res)
              }
            })
          })
        }
      })
    },

自定义的下载文件函数

  downloadFile(fileID, onCall = () => {}) {
    return new Promise((resolve) => {
      const task = wx.cloud.downloadFile({
        fileID,
        success: res => resolve(res),
      })
      task.onProgressUpdate((res) => {
        if (onCall(res) == false) {
          task.abort()
        }
      })
    })
  },

自定义创建session的函数

  createInferenceSession(modelPath) {
    return new Promise((resolve, reject) => {
      this.session = wx.createInferenceSession({
        model: modelPath,
        precisionLevel : 4,
        allowNPU : false,
        allowQuantize: false,
      });

      // 监听error事件
      this.session.onError((error) => {
        console.error(error);
        reject(error);
      });
      this.session.onLoad(() => {
        resolve();
      });
    })
  },

自定义的图像预处理函数

该函数接收帧数据(RGBA一维数组)和在外面初始化的Float32Array数组,执行归一化、去除透明度通道。

    preProcess(frame,dstInput){
      return new Promise(resolve=>{
        const origData = new Uint8Array(frame.data);
        for(var j=0;j<frame.height;j++){
          for(var i=0;i<frame.width;i++){
            dstInput[i*3+this.data.imgW*j*3]=origData[i*4+j*frame.width*4]/255
            dstInput[i*3+1+this.data.imgW*j*3]=origData[i*4+1+j*frame.width*4]/255
            dstInput[i*3+2+this.data.imgW*j*3]=origData[i*4+2+j*frame.width*4]/255
          }
        }
        resolve();
      })
    },

自定义的推理函数

推理接口接收数个键值对input,具体需要参照自己的onnx模型,在Netron查看相应的模型信息

我这里只有1个输入,对应的名字为"images",接收(1,3,640,640)形状的图像数组

我这里的onnx输出数组是1*6*10的,代表有10个检测框,还有4个坐标信息+类别编号+置信度。我的输出的数组名字叫 output0,注意参照自己的onnx输出名

接着就是获取最大置信度所在索引并按照索引取出其对应框的信息和类别编号

然后绘制在canvas上

为了在没有检测到物体时不绘制出框,检测到物体时绘制检测框,就先获取<canvas>对象,清空画布,再对session输出的数据进行后处理,然后给个阈值判断是否画框。

    infer(imgData){
      this.session.run({
        'images':{
          shape:[1,3,this.data.imgH,this.data.imgW],
          data:imgData.buffer,
          type:'float32',
        }
      // 获得运行结果后
      }).then((res)=>{
        let results=new Float32Array(res.output0.data)
        // 获取canvas对象,填上id,这里对应”c1“
        wx.createSelectorQuery().select('#c1')
        .fields({node:true,size:true})
        .exec((res)=>{
          const canvas=res[0].node
          const ctx=canvas.getContext('2d')
          canvas.width=wx.getSystemInfoSync().windowWidth*0.9
          canvas.height=this.data.windowHeight
          // 对session数据进行后处理
          this.postProcess(results).then((index)=>{
            // 清空画布
            ctx.clearRect(0,0,canvas.width,canvas.height)
            // 大于阈值,就认为检测到物体
            if(this.data.conf>0.5){
              this.setData({
                class_name:'检测到苹果'
              })
              // 这里需要参考自己的session输出的数组上对应位置的具体含义
              // 比如我的session输出1*6*10的一维数组,可以看作6*10的二维数组,
              // 有6行数据,第一行对应中心点x坐标,第二行对应中心点y坐标,
              // 第3行对应检测框的w宽度,第4行对应检测框的h长度,
              // 第5行对应置信度,第6行对应类别编号
              var x=results[index]
              var y=results[10+index]
              var w=results[2*10+index]
              var h=results[3*10+index]
              var x1=Math.round(x-w/2)
              var y1=Math.round(y-h/2)
              var x2=Math.round(x+w/2)
              var y2=Math.round(y+h/2)
              ctx.strokeStyle='red'
              ctx.lineWidth=2
              ctx.strokeRect(x1,y1,x2,y2)
            }
          })
        })
      })
    },

 自定义的后处理函数

初始化置信度和index,对10个检测框进行遍历,取出置信度最大元素所在index,然后更新到全局变量中,这里设定阈值为0.5. 此函数接收session输出的数组,返回index

    postProcess(results){
      return new Promise((resolve)=>{
        var maxConf=results[10*4]
        var index=0
        for(var i=1;i<10;i+=1){
          var conf=results[10*4+i]
          if(conf>0.5 & maxConf<conf){
            maxConf=conf
            index=i
          }
        }
        this.setData({
          conf:maxConf,
          class_name:'未检测出苹果'
        })
        resolve(index)
      })
    },

代码总览

index.js

Page({
    data: {
        imagePath: '/images/tree.png',
        windowHeight:wx.getSystemInfoSync().windowWidth*1.197,
        imgH:640,
        imgW:640,
        conf:0,
        class_name:'未检测到红火蚁',
    },
    onLoad(){
      const context = wx.createCameraContext();
      this.listener=context.onCameraFrame((frame)=>this.CamFramCall(frame));
      this.initSession()
    },
    initSession(){
      const cloudPath='cloud://cloud1-8gcwcxqrb8722e9e.636c-cloud1-8gcwcxqrb8722e9e-1324077753/best.onnx'
      const lastIndex=cloudPath.lastIndexOf('/')
      const filename=cloudPath.substring(lastIndex+1)
      const modelPath=`${wx.env.USER_DATA_PATH}/`+filename
      wx.getFileSystemManager().access({
        path:modelPath,
        success:(res)=>{
          console.log('file already exist')
          this.createInferenceSession(modelPath)
          setInterval(()=>{this.listener.start()},1000)
        },
        fail:(res)=>{
          console.error(res)
          wx.cloud.init()
          console.log('begin download model')
          wx.showLoading({
            title: '加载检测中',
          })
          this.downloadFile(cloudPath,function(r) {
            console.log(`下载进度:${r.progress}%,已下载${r.totalBytesWritten}B,共${r.totalBytesExpectedToWrite}B`)
          }).then(result=>{
            wx.getFileSystemManager().saveFile({
              tempFilePath:result.tempFilePath,
              filePath:modelPath,
              success:(res)=>{
                const modelPath=res.savedFilePath
                console.log('save onnx model at path:'+modelPath)
                this.createInferenceSession(modelPath)
                wx.hideLoading()
                setInterval(()=>{this.listener.start()},1000)
              },
              fail:(res)=>{
                console.error(res)
              }
            })
          })
        }
      })
    },
    createInferenceSession(modelPath){
      return new Promise((resolve,reject)=>{
        this.session=wx.createInferenceSession({
          model: modelPath,
          precesionLevel:4,
          allowNPU:false,
          allowQuantize:false,
        })
        this.session.onError((error) => {
          console.error(error)
          reject(error)
        })
        this.session.onLoad(()=>{
          resolve()
        })
      })
    },
    CamFramCall(frame){
      this.setData({
        windowHeight:frame.height/frame.width*wx.getSystemInfoSync().windowWidth*0.9
      })
      var dstInput=new Float32Array(3*this.data.imgH*this.data.imgW).fill(255)
      this.preProcess(frame,dstInput)
      this.infer(dstInput)
      console.log('完成一次帧循环')
      this.listener.stop()
    },
    preProcess(frame,dstInput){
      return new Promise(resolve=>{
        const origData = new Uint8Array(frame.data);
        for(var j=0;j<frame.height;j++){
          for(var i=0;i<frame.width;i++){
            dstInput[i*3+this.data.imgW*j*3]=origData[i*4+j*frame.width*4]
            dstInput[i*3+1+this.data.imgW*j*3]=origData[i*4+1+j*frame.width*4]
            dstInput[i*3+2+this.data.imgW*j*3]=origData[i*4+2+j*frame.width*4]
          }
        }
        resolve();
      })
    },
    postProcess(results){
      return new Promise((resolve)=>{
        var maxConf=results[10*4]
        var index=0
        for(var i=1;i<10;i+=1){
          var conf=results[10*4+i]
          if(conf>0.5 & maxConf<conf){
            maxConf=conf
            index=i
          }
        }
        this.setData({
          conf:maxConf,
          class_name:'未检测到红火蚁'
        })
        resolve(index)
      })
    },
    infer(imgData){
      this.session.run({
        'images':{
          shape:[1,3,this.data.imgH,this.data.imgW],
          data:imgData.buffer,
          type:'float32',
        }
      }).then((res)=>{
        let results=new Float32Array(res.output0.data)
        wx.createSelectorQuery().select('#c1')
        .fields({node:true,size:true})
        .exec((res)=>{
          const canvas=res[0].node
          const ctx=canvas.getContext('2d')
          canvas.width=wx.getSystemInfoSync().windowWidth*0.9
          canvas.height=this.data.windowHeight
          this.postProcess(results).then((index)=>{
            ctx.clearRect(0,0,canvas.width,canvas.height)
            if(this.data.conf>0.5){
              this.setData({
                class_name:'检测到红火蚁'
              })
              var x=results[index]
              var y=results[8400+index]
              var w=results[2*8400+index]
              var h=results[3*8400+index]
              var x1=Math.round(x-w/2)
              var y1=Math.round(y-h/2)
              var x2=Math.round(x+w/2)
              var y2=Math.round(y+h/2)
              ctx.strokeStyle='red'
              ctx.lineWidth=2
              ctx.strokeRect(x1,y1,x2,y2)
            }
          })
        })
      })
    },
    downloadFile(fileID,onCall=()=>{}){
      return new Promise((resolve)=>{
        const task=wx.cloud.downloadFile({
          fileID,
          success:res=>resolve(res),
        })
        task.onProgressUpdate((res)=>{
          if(onCall(res)==false){
            task.abort()
          }
        })
      })
    },
})

 index.wxss

.c1{
  width: 100%;
  align-items: center;
  text-align: center;
  display: flex;
  flex-direction: column;
}
#myCanvas{
  width: 100%;
  height: 100%;
}

index.wxml

<view class="c1">
<camera class="camera" binderror="error" mode="normal" style="width: 90%; height: {{windowHeight}}px;">
  <canvas id="c1" type="2d"></canvas>
</camera>
<view>结果:{{class_name}}</view>
<view>置信度:{{conf}}</view>
</view> 

flask部署

微信小程序负责把图像数据或帧数据传到服务器,在服务器用flask搭建相关模型运行环境,将接收到的图像数据或帧数据预处理后输入模型里,在将结果返回给微信小程序,微信小程序再显示结果。

我这里给的例子是传送帧数据的,也就是实时检测。

但是目前存在一个问题,速度,检测框的速度跟不上物体移动,只能慢速检测,一旦提高频率小程序就抓不到实时帧。

前端

在前端,获得帧数据后,因为帧数据的格式是一维RGBA数组,为了将其转成png,方便服务器处理,把帧数据绘制到画布上,再导出为png送入服务器。接收到服务器的结果后,将检测框绘制到相机的界面,需要在<camera>标签里加上<canvas>标签,然后画上矩形框,并在下方显示分类结果。

主体代码框架

初始化页面数据,camH是<camera>组件在展示页面的高度,k是比例系数

每个系统运行小程序,所导出的frame大小是不同的,为了更好的画检测框:首先模型接收的是frame,其运行的结果的检测框坐标数据是基于frame的。而<camera>组件的展示大小也是要设定的,我把<camera>的宽度定死在整个页面宽度的0.9(在wxss中设定),然后使<camera>与frame成比例(就只需要设定camH,我这里给了一个初始值1.2,后面的程序会更精确的调),<camera>与frame的比例系数为k,再让camera中的画布<canvas>完全贴合于其父元素<camera>,只要把模型跑出的坐标乘以比例系数k即可映射到<camera>上。

  onLoad(){
    // 执行自定义的初始化函数
    this.init().then(()=>{
      // 创建相机上下文
      const context = wx.createCameraContext();
      // 设定监听回调函数
      this.listener=context.onCameraFrame(frame=>this.CamFramCall(frame));
      // 每500ms开启一次监听
      setInterval(()=>{this.listener.start()}, 500);
    })
  },

自定义的初始化函数

 为了设定<camera>的高和比例系数,需要知道frame的尺寸,所以这里调用了一次相机帧。而后面调用相机帧是为了获取帧数据,两者目的不同。

  init(){
    return new Promise(resolve=>{
      const context = wx.createCameraContext();
      const listener=context.onCameraFrame(frame=>{
        this.setData({
          camH:wx.getSystemInfoSync().windowWidth*0.9*frame.height/frame.width,
          k:wx.getSystemInfoSync().windowWidth*0.9/frame.width
        })
        listener.stop()
    })
    listener.start()
    resolve()
    })
  },

 实时帧回调函数

在回调函数里,将接收到的数据转base64,然后将数据传到服务器,最后停止监听,至此完成一帧

  CamFramCall(frame){
    this.base64ToPNG(frame).then(result=>{
      this.interWithServer({'img':result})
      console.log('完成一次帧循环')
      this.listener.stop()
    })
  },

自定义帧数据转base64的函数

参考http://t.csdnimg.cn/2hc7k

这里增加了异步编程的语句,更合理

  base64ToPNG(frame){
    return new Promise(resolve=>{
      const query = wx.createSelectorQuery()
      query.select('#canvas')
        .fields({node:true,size:true})
        .exec((res)=>{
          const canvas=res[0].node
          const ctx=canvas.getContext('2d')
          canvas.width=frame.width
          canvas.height=frame.height
          var imageData=ctx.createImageData(canvas.width,canvas.height)
          var ImgU8Array = new Uint8ClampedArray(frame.data);
          for(var i=0;i<ImgU8Array.length;i+=4){
            imageData.data[0+i]=ImgU8Array[i+0]
            imageData.data[1+i]=ImgU8Array[i+1]
            imageData.data[2+i]=ImgU8Array[i+2]
            imageData.data[3+i]=ImgU8Array[i+3]
          }
          ctx.putImageData(imageData,0,0,0,0,canvas.width,canvas.height)
          resolve(canvas.toDataURL())
        })
    })
  },

自定义传数据到服务器函数 

  interWithServer(imgData){
    const header = {
      'content-type': 'application/x-www-form-urlencoded'
    };
    wx.request({
      // 填上自己的服务器地址
      url: 'http://172.16.3.186:5000/predict',
      method: 'POST',
      header: header,
      data: imgData,
      success: (res) => {
      // 返回的坐标数据,调用自定义的画检测框函数
this.drawRect(res.data['conf'],res.data['x'],res.data['y'],res.data['w'],res.data['h'])
      },
      fail: () => {
        wx.showToast({
          title: 'Failed to connect server!',
          icon: 'none',
        });
      }
    });
  },

自定义的画检测框函数 

  drawRect(conf,x,y,w,h){
    // 填上<camera>内<canvas>的id
    wx.createSelectorQuery().select('#c1')
    .fields({node:true,size:true})
        .exec((res)=>{
          const canvas=res[0].node
          const ctx=canvas.getContext('2d')
          // 设置宽高,完全填充于<camera>组件的大小
          canvas.width=wx.getSystemInfoSync().windowWidth*0.9
          canvas.height=this.data.camH
          // 清空画布,避免遗留上次的检测框
          ctx.clearRect(0,0,canvas.width,canvas.height)
          // 如果置信度大于0.5,才画框
          if(conf>0.5){
            ctx.strokeStyle='red'
            ctx.lineWidth=2
            const k =this.data.k
            // 经过真机测试,发现在x和y上乘以比例系数即可,较为精确
            // 虽然理论上要按比例计算,但可以根据实际的情况做出一点调整,对检测框进行修正
            ctx.strokeRect(k*x,k*y,x+w,y+h)
          }
        })
  },

index.js

Page({
  data: {
    camH:wx.getSystemInfoSync().windowWidth*1.2,
    k:1
  },
  onLoad(){
    this.init().then(()=>{
      const context = wx.createCameraContext();
      this.listener=context.onCameraFrame(frame=>this.CamFramCall(frame));
      setInterval(()=>{this.listener.start()}, 500);
    })
  },
  init(){
    return new Promise(resolve=>{
      const context = wx.createCameraContext();
      const listener=context.onCameraFrame(frame=>{
        this.setData({
          camH:wx.getSystemInfoSync().windowWidth*0.9*frame.height/frame.width,
          k:wx.getSystemInfoSync().windowWidth*0.9/frame.width
        })
        listener.stop()
    })
    listener.start()
    resolve()
    })
  },
  CamFramCall(frame){
    this.base64ToPNG(frame).then(result=>{
      this.interWithServer({'img':result})
      console.log('完成一次帧循环')
      this.listener.stop()
    })
  },
  drawRect(conf,x,y,w,h){
    wx.createSelectorQuery().select('#c1')
    .fields({node:true,size:true})
        .exec((res)=>{
          const canvas=res[0].node
          const ctx=canvas.getContext('2d')
          canvas.width=wx.getSystemInfoSync().windowWidth*0.9
          canvas.height=this.data.camH
          ctx.clearRect(0,0,canvas.width,canvas.height)
          if(conf>0.5){
            ctx.strokeStyle='red'
            ctx.lineWidth=2
            const k =this.data.k
            ctx.strokeRect(k*x,k*y,x+w,y+h)
          }
        })
  },
  interWithServer(imgData){
    const header = {
      'content-type': 'application/x-www-form-urlencoded'
    };
    wx.request({
      url: 'http://172.16.3.186:5000/predict',
      method: 'POST',
      header: header,
      data: imgData,
      success: (res) => {
        this.drawRect(res.data['conf'],res.data['x'],res.data['y'],res.data['w'],res.data['h'])
      },
      fail: () => {
        wx.showToast({
          title: 'Failed to connect server!',
          icon: 'none',
        });
      }
    });
  },
  base64ToPNG(frame){
    return new Promise(resolve=>{
      const query = wx.createSelectorQuery()
      query.select('#tranPng')
        .fields({node:true,size:true})
        .exec((res)=>{
          const canvas=res[0].node
          const ctx=canvas.getContext('2d')
          canvas.width=frame.width
          canvas.height=frame.height
          var imageData=ctx.createImageData(canvas.width,canvas.height)
          var ImgU8Array = new Uint8ClampedArray(frame.data);
          for(var i=0;i<ImgU8Array.length;i+=4){
            imageData.data[0+i]=ImgU8Array[i+0]
            imageData.data[1+i]=ImgU8Array[i+1]
            imageData.data[2+i]=ImgU8Array[i+2]
            imageData.data[3+i]=ImgU8Array[i+3]
          }
          ctx.putImageData(imageData,0,0,0,0,canvas.width,canvas.height)
          resolve(canvas.toDataURL())
        })
    })
  },
})

 index.wxml

注意,<camera>中的<canvas>是为了画检测框,另一个<canvas>是为了将frame数据转base64Png。

<view class="c1">
  <camera class="camera" binderror="error" style="width: 90%; height: {{camH}}px;">
    <canvas id="c1" type="2d"></canvas>
  </camera>
  <canvas id="tranPng" hidden="true" type="2d"></canvas>
</view> 

index.wxss

.c1{
  width: 100%;
  align-items: center;
  text-align: center;
  display: flex;
  flex-direction: column;
}
#c1{
  width: 100%;
  height: 100%;
}
#canvas{
  width: 100%;
}

后端

接收数据,预处理图像,送入模型,得到初始结果,转化初始结果得到最终结果,返回数据到前端

这里仅作演示,不提供完整项目运行代码和依赖项

from PIL import Image
from gevent import monkey
from flask import Flask, jsonify, request
from gevent.pywsgi import WSGIServer
import cv2
import paddle
import numpy as np
from ppdet.core.workspace import load_config
from ppdet.engine import Trainer
from ppdet.metrics import get_infer_results
from ppdet.data.transform.operators import NormalizeImage, Permute
import base64
import io

app = Flask(__name__)
monkey.patch_all()
# 准备基础的参数
config_path = 'face_detection\\blazeface_1000e.yml'
cfg = load_config(config_path)
weight_path = '202.pdparams'
infer_img_path = '1.png'
cfg.weights = weight_path
bbox_thre = 0.8
paddle.set_device('cpu')
# 创建所需的类
trainer = Trainer(cfg, mode='test')
trainer.load_weights(cfg.weights)
trainer.model.eval()
normaler = NormalizeImage(mean=[123, 117, 104], std=[127.502231, 127.502231, 127.502231], is_scale=False)
permuter = Permute()
    

model_dir = "face_detection\\blazeface_1000e.yml" # 模型路径
save_path = "output"  # 推理结果保存路径



def infer(img, threshold=0.2):
    img = img.replace("data:image/png;base64,", "")
    img = base64.b64decode(img)
    img = Image.open(io.BytesIO(img))
    img = img.convert('RGB')
    img = np.array(img)
    # 准备数据字典
    data_dict = {'image': img}
    data_dict = normaler(data_dict)
    data_dict = permuter(data_dict)
    h, w, c = img.shape
    data_dict['im_id'] = paddle.Tensor(np.array([[0]]))
    data_dict['im_shape'] = paddle.Tensor(np.array([[h, w]], dtype=np.float32))
    data_dict['scale_factor'] = paddle.Tensor(np.array([[1., 1.]], dtype=np.float32))
    data_dict['image'] = paddle.Tensor(data_dict['image'].reshape((1, c, h, w)))
    data_dict['curr_iter'] = paddle.Tensor(np.array([0]))
    # 进行预测
    outs = trainer.model(data_dict)
    # 对预测的数据进行后处理得到最终的bbox信息
    for key in ['im_shape', 'scale_factor', 'im_id']:
        outs[key] = data_dict[key]
    for key, value in outs.items():
        outs[key] = value.numpy()
    clsid2catid, catid2name = {0: 'face'}, {0: 0}
    batch_res = get_infer_results(outs, clsid2catid)
    for sub_dict in batch_res['bbox']:
        if sub_dict['score'] > bbox_thre:
            image_id=sub_dict['image_id']
            category_id=sub_dict['category_id']
            x,y,w,h=[int(i) for i in sub_dict['bbox']]
            conf=sub_dict['score']
            print(x,y,w,h,conf)
            return jsonify({'conf':conf,'x':x,'y':y,'w':w,'h':h})
        else:
            return jsonify({'conf':0,'x':0,'y':0,'w':0,'h':0})

    

@app.route('/predict', methods=['POST'])
def predict():
    if request.method == 'POST':
        img = request.form.get('img')
        w=request.form.get('w')
        h=request.form.get('h')
        return infer(img)
        
if __name__ == '__main__':
    server = WSGIServer(('0.0.0.0', 5000), app)
    server.serve_forever()

  • 14
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: 要将微信小程序部署到Tomcat上,需要进行以下步骤: 1. 创建一个Java Web项目:首先在Tomcat上创建一个新的Java Web项目,可以使用Eclipse或IntelliJ等IDE工具来创建。 2. 配置项目结构:在项目的结构中,确保有一个Web文件夹,其中包含一个WEB-INF文件夹。在WEB-INF文件夹下创建一个lib文件夹,并将所有的依赖jar包放在该文件夹中。同时,创建一个classes文件夹用于存放编译后的class文件。 3. 编写后端代码:在项目下创建一个Java类,作为后端的处理程序。该类需要继承HttpServlet,并重写doGet和doPost方法以处理来自小程序的请求。 4. 配置web.xml:在WEB-INF文件夹下创建web.xml文件,配置Servlet的映射。在web.xml中,配置一个Servlet的映射,将该Servlet映射到指定的URL,并定义Servlet的名称和类路径。 5. 构建小程序前端代码:在微信小程序的前端代码中,通过调用接口请求后端数据。可以使用WeUI、Vant等前端框架来设计和构建小程序的界面。 6. 部署到Tomcat:将编写完毕的所有后端和前端代码打包成一个war文件,然后将该war文件部署到Tomcat的webapps文件夹下。在Tomcat的bin目录下启动Tomcat服务器。 7. 测试:访问小程序,并确认与后端代码的连接是否正常。可以使用微信开发者工具进行在线调试和排查问题。 通过以上步骤,就可以将微信小程序部署到Tomcat服务器上,实现后端与前端的交互和数据传输。 ### 回答2: 微信小程序是一种基于微信平台的应用程序,而Tomcat是一种支持Java应用程序运行的服务器。因此,要将微信小程序部署到Tomcat服务器上,需要以下步骤: 1. 首先,确保已经安装并配置好Tomcat服务器。可以从Tomcat官方网站上下载适合的版本,并按照官方文档进行安装和配置。 2. 在Tomcat服务器中创建一个新的Web应用程序目录。这样,我们可以将微信小程序的文件和代码放在这个目录下。 3. 将微信小程序的代码和资源文件拷贝到Tomcat的Web应用程序目录中。确保包含小程序的所有文件和文件夹都被正确地放置在了相应的位置。 4. 配置Tomcat服务器,使其能够正确地处理微信小程序的请求。这包括设置Tomcat的端口号、启用SSL证书等。 5. 启动Tomcat服务器,并确保它能够成功地部署和运行微信小程序。可以通过浏览器访问Tomcat的管理界面,来验证小程序是否已正确部署。 6. 如果一切正常,通过微信开发者工具上传小程序代码,并配置相应的AppID和AppSecret。 7. 在微信公众平台上进行小程序的设置和验证。确保小程序已经和Tomcat服务器成功地连接起来,并能够正常地响应用户的请求。 总之,将微信小程序部署到Tomcat服务器上,需要确保Tomcat正确安装和配置,并将小程序的代码和资源文件放置在合适的位置。然后,通过配置Tomcat服务器,将其与微信小程序相连接,最后验证小程序是否成功运行。 ### 回答3: 微信小程序是一种基于微信平台的轻量级应用,而Tomcat是一个用于构建和部署Java 网络应用程序的开源Web服务器。由于微信小程序是基于前端技术开发的,而Tomcat是用于托管和运行Java后端应用的,所以不能直接将微信小程序部署到Tomcat服务器上。 但是,可以通过在Tomcat中部署一个与微信小程序交互的后端应用来实现小程序的功能。通常情况下,这个后端应用会使用一些Java框架(如Spring Boot)来处理数据请求、对接微信开放平台的API等。 具体的部署步骤如下: 1. 开发后端应用:使用Java编写一个后端应用,该应用可以处理来自微信小程序的请求,实现小程序的业务逻辑。可以使用一些开发框架来简化开发流程。 2. 构建后端应用:将开发好的后端应用打包成一个可执行的WAR文件(Web应用归档文件)。 3. 部署到Tomcat服务器:将WAR文件拷贝到Tomcat服务器的webapps目录下,并启动Tomcat服务器。 4. 配置Tomcat服务器:根据后端应用的需求,可能需要在Tomcat的配置文件中设置一些参数,如数据库连接、API密钥等。 5. 启动后端应用:访问Tomcat服务器的URL,可以看到后端应用启动成功的页面。 6. 连接微信小程序:在微信小程序的代码中,通过HTTP请求的方式连接到Tomcat服务器,并传输数据。 通过以上步骤的部署,可以实现将与微信小程序交互的后端应用部署在Tomcat服务器上,使得小程序可以与后台进行数据交互,并实现各种功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值