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

这篇具有很好参考价值的文章主要介绍了在微信小程序部署AI模型的几种方法。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

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

onnx部署

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

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

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

代码框架

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

初始化session

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

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

  InitSession()
  {
    return new Promise(resolve=>{
      const cloudPath = 'cloud://cloud1-8gcwcxqrb8722e9e.636c-cloud1-8gcwcxqrb8722e9e-1324077753/mobilnet.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,
      success: (res) =>
      {
        console.log("文件已经存在")
        // 创建session
        this.createInferenceSession(modelPath)
        // 监听帧,频率为1秒1次
        setInterval(this.oneFrame, 1000)
        resolve()
      },
      fail: (res) => {
        // 文件不存在
        console.error(res)
        wx.cloud.init();
        console.log("开始下载模型");
        // 调用自定义函数下载文件
        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) => { // 注册回调函数
              console.log(res)
              const modelPath = res.savedFilePath;
              console.log("保存模型到路径: " + modelPath)
              // 创建session
              this.createInferenceSession(modelPath)
              // 监听帧,频率为1秒1次
              setInterval(this.oneFrame, 1000)
              resolve()
            },
            fail(res) {
              console.error(res)
            }
          })
        });
      }
      })
    })

自定义的下载文件函数

  downloadFile(fileID, onCall = () => {}) {
    return new Promise((resolve, reject) => {
      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();
      });
    })
  },

自定义处理帧函数

就是上面初始化session步骤里面 创建session后 按预设频率执行的函数

开启相机监听,在回调函数内获取帧数据、处理帧数据、开始推理、关闭监听

  oneFrame(){
    const context=wx.createCameraContext()
    const camCallback=(frame)=>{
      // 处理图片数据
      var dstInput=new Float32Array(this.data.imageChannel*this.data.imageWidth*this.data.imageHeight)
      this.preProcess(frame,dstInput)
      // 推理得到结果
      this.infer(dstInput)
      // 关闭监听
      listener.stop()
    }
    const listener=context.onCameraFrame(camCallback)
    listener.start()
  },

自定义的图像处理函数

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

  preProcess(frame, dstInput) {
    return new Promise((resolve, reject) =>
    {
      const origData = new Uint8Array(frame.data);
      const hRatio = frame.height / this.data.imageHeight;
      const wRatio = frame.width / this.data.imageWidth;
      const origHStride = frame.width * 4;
      const origWStride = 4;
      const mean = [0.485, 0.456, 0.406]
      // Reverse of std = [0.229, 0.224, 0.225]
      const reverse_div = [4.367, 4.464, 4.444]
      const ratio = 1 / 255.0
      const normalized_div = [ratio / reverse_div[0], ratio * reverse_div[1], ratio * reverse_div[2]];
      const normalized_mean = [mean[0] * reverse_div[0], mean[1] * reverse_div[1], mean[2] * reverse_div[2]];
      var idx = 0;
      for (var c = 0; c < this.data.imageChannel; ++c)
      {
        for (var h = 0; h < this.data.imageHeight; ++h)
        {
          const origH = Math.round(h * hRatio);
          const origHOffset = origH * origHStride;
          for (var w = 0; w < this.data.imageWidth; ++w)
          {
            const origW = Math.round(w * wRatio);
            const origIndex = origHOffset + origW * origWStride + c;
            const val = origData[origIndex] * (normalized_div[c]) - normalized_mean[c];
            dstInput[idx] = val;
            idx++;
          }
        }
      } 
      resolve();
    });
  },

自定义的推理函数

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

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

我这里有2个输出,对应的名字是“794”和“output”,分别对应相应类别的置信度(1*10*2)&框的坐标信息(1*10*4),这里的10对应10个预测框,2代表有2个类别

接着就是获取某一类别(比如前景)最大置信度的索引并取出其框的信息

然后绘制在canvas上

当然也可以设置阈值比如0.5,前景类别置信度大于0.5的就保留,然后根据得到的index取出框的信息,绘制到canvas上,或者只取类别和对应的置信度,根据自己的需求处理

  infer(imgData){
    this.session.run({
      "images":{
        shape: [1, this.data.imageChannel, this.data.imageHeight, this.data.imageWidth],
        data: imgData.buffer,
        type: 'float32',
      }
    }).then((res)=>{
      let box = new Float32Array(res.output.data)
      let score = new Float32Array(res[794].data)
      // console.log(box)
      let num = new Float32Array(score)
      var maxVar = num[0];
      var index = 0;
      for (var i = 0; i < num.length; i+=2)
      {
        if (maxVar < num[i])
        {
            maxVar = num[i]   
            index = i/2   
        }
      }
      this.setData({
        xmin:box[index*4],
        xmax:box[index*4+2],
        ymin:box[index*4+1],
        ymax:box[index*4+3]
      })
      this.drawRectangle()
    })
  },

自定义的绘制框函数

这里用的是微信新的canvas接口

  drawRectangle(){
    wx.createSelectorQuery().select('#myCanvas')
      .fields({node:true,size:true})
          .exec((res)=>{
            const canvas=res[0].node
            const ctx=canvas.getContext('2d')
            const dpr = wx.getSystemInfoSync().pixelRatio
            canvas.width = res[0].width * dpr
            canvas.height = res[0].height * dpr
            ctx.scale(dpr, dpr)
            ctx.strokeStyle='red'
            ctx.lineWidth=2
            console.log(this.data.xmin, this.data.ymin, this.data.xmax, this.data.ymax)
            ctx.strokeRect(this.data.xmin, this.data.ymin, this.data.xmax, this.data.ymax,canvas.width,canvas.height)
          })
  }

代码总览

index.js

Page({
  session:null,
  data: {
    src : '',
    windowWidth:0,
    imageWidth : 300,
    imageHeight : 300,
    imageChannel : 3,
    xmin:0,
    ymin:0,
    xmax:0,
    ymax:0
  },
  onLoad(){
    this.setData({
      windowWidth:wx.getSystemInfoSync().windowWidth*0.9
    })
    this.InitSession()
  },
  oneFrame(){
    const context=wx.createCameraContext()
    const camCallback=(frame)=>{
      // 处理图片数据
      var dstInput=new Float32Array(this.data.imageChannel*this.data.imageWidth*this.data.imageHeight)
      this.preProcess(frame,dstInput)
      // 推理得到结果
      this.infer(dstInput)
      // 关闭监听
      listener.stop()
    }
    const listener=context.onCameraFrame(camCallback)
    listener.start()
  },
  downloadFile(fileID, onCall = () => {}) {
    return new Promise((resolve, reject) => {
      const task = wx.cloud.downloadFile({
        fileID,
        success: res => resolve(res),
      })
      task.onProgressUpdate((res) => {
        if (onCall(res) == false) {
          task.abort()
        }
      })
    })
  },
  preProcess(frame, dstInput) {
    return new Promise((resolve, reject) =>
    {
      const origData = new Uint8Array(frame.data);
      const hRatio = frame.height / this.data.imageHeight;
      const wRatio = frame.width / this.data.imageWidth;
      const origHStride = frame.width * 4;
      const origWStride = 4;
      const mean = [0.485, 0.456, 0.406]
      // Reverse of std = [0.229, 0.224, 0.225]
      const reverse_div = [4.367, 4.464, 4.444]
      const ratio = 1 / 255.0
      const normalized_div = [ratio / reverse_div[0], ratio * reverse_div[1], ratio * reverse_div[2]];
      const normalized_mean = [mean[0] * reverse_div[0], mean[1] * reverse_div[1], mean[2] * reverse_div[2]];
      var idx = 0;
      for (var c = 0; c < this.data.imageChannel; ++c)
      {
        for (var h = 0; h < this.data.imageHeight; ++h)
        {
          const origH = Math.round(h * hRatio);
          const origHOffset = origH * origHStride;
          for (var w = 0; w < this.data.imageWidth; ++w)
          {
            const origW = Math.round(w * wRatio);
            const origIndex = origHOffset + origW * origWStride + c;
            const val = origData[origIndex] * (normalized_div[c]) - normalized_mean[c];
            dstInput[idx] = val;
            idx++;
          }
        }
      } 
      resolve();
    });
  },
  infer(imgData){
    this.session.run({
      "images":{
        shape: [1, this.data.imageChannel, this.data.imageHeight, this.data.imageWidth],
        data: imgData.buffer,
        type: 'float32',
      }
    }).then((res)=>{
      let box = new Float32Array(res.output.data)
      let score = new Float32Array(res[794].data)
      // console.log(box)
      let num = new Float32Array(score)
      var maxVar = num[0];
      var index = 0;
      for (var i = 0; i < num.length; i+=2)
      {
        if (maxVar < num[i])
        {
            maxVar = num[i]   
            index = i/2   
        }
      }
      this.setData({
        xmin:box[index*4],
        xmax:box[index*4+2],
        ymin:box[index*4+1],
        ymax:box[index*4+3]
      })
      this.drawRectangle()
    })
  },
  InitSession()
  {
    return new Promise(resolve=>{
      const cloudPath = 'cloud://cloud1-8gcwcxqrb8722e9e.636c-cloud1-8gcwcxqrb8722e9e-1324077753/mobilnet.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,
      success: (res) =>
      {
        console.log("file already exist at: " + modelPath)
        this.createInferenceSession(modelPath)
        setInterval(this.oneFrame, 1000)
        resolve()
      },
      fail: (res) => {
        console.error(res)
        wx.cloud.init();
        console.log("begin download model");
        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) => { // 注册回调函数
              console.log(res)
              const modelPath = res.savedFilePath;
              console.log("save onnx model at path: " + modelPath)
              this.createInferenceSession(modelPath)
              setInterval(this.oneFrame, 1000)
              resolve()
            },
            fail(res) {
              console.error(res)
            }
          })
        });
      }
      })
    })
  },
  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();
      });
    })
  },
  drawRectangle(){
    wx.createSelectorQuery().select('#myCanvas')
      .fields({node:true,size:true})
          .exec((res)=>{
            const canvas=res[0].node
            const ctx=canvas.getContext('2d')
            const dpr = wx.getSystemInfoSync().pixelRatio
            canvas.width = res[0].width * dpr
            canvas.height = res[0].height * dpr
            ctx.scale(dpr, dpr)
            ctx.strokeStyle='red'
            ctx.lineWidth=2
            console.log(this.data.xmin, this.data.ymin, this.data.xmax, this.data.ymax)
            ctx.strokeRect(this.data.xmin, this.data.ymin, this.data.xmax, this.data.ymax,canvas.width,canvas.height)
          })
  }
})

 index.wxss

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

index.wxml

<view class="c1">
<camera class="camera" binderror="error" mode="normal" style="width: 90%; height: {{windowWidth}}px;">
  <canvas id="myCanvas" type="2d"></canvas>
</camera>
</view> 

flask部署

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

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

前端

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

主体代码框架

Page({
  data: {
    windowWidth:wx.getSystemInfoSync().windowWidth*1.33,
    boxNum:'',
  },
  // 自定义实时检测的频率,这里是800ms检测一次
  //  具体见此地址
  onLoad(){
    setInterval(this.oneProcessFrame, 800);
  },
})
  oneProcessFrame(){
    const context = wx.createCameraContext();
    const data={"pngData":null}
    const CamFramCall = (frame)=>{
      // 调整显示页面的相机画面,为了使显示页面的横宽比等于frame数据的横宽比
      // 在画框的时候,模型跑出来的检测框坐标是相对于输入的图像的大小
      // 如果显示画面和输入框的比例不匹配,就会出现检测框不完整或者检测框有部分跑到画面外的情况
      // 微信小程序的frame,我没有找到官方提供的可以修改尺寸的API,所以用了这个办法
      //当然还有一种思路,将frame进行裁剪,使frame包含的图片信息正好对应显示画面的信息(像素一一对应)
      this.setData({
        windowWidth:frame.height/frame.width*wx.getSystemInfoSync().windowWidth*0.9
      })
      // 调用自定义函数将frame转png,然后把png数据绑定到传送给服务器的data
      // 再将data传给服务器
      // 这里用了异步编程,只有帧数据顺利转成png才发送给服务器,确保模型接收正确数据
      this.base64ToPNG(frame).then((pngData)=>{
        data["pngData"]=pngData
        this.interWithServer(data)
      })
      // 这里已经处理完一帧的数据,如果不关闭监听相机,那么微信小程序会持续触发相机帧数据回调函数,导致小程序卡顿,资源浪费
      console.log('完成一次帧循环')
      listener.stop()
    }
    // 定义相机帧回调函数
    const listener = context.onCameraFrame(CamFramCall);
    开启监听
    listener.start()
  },

自定义帧数据转base64的函数

参考

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

  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(data){
    const header = {
      'content-type': 'application/x-www-form-urlencoded'
    };
    wx.request({
      // 填上自己的服务器地址(下面这个是我的服务器内网地址,仅供展示)
      url: 'http://172.16.3.186:5000/predict',
      method: 'POST',
      header: header,
      data: data,
      success: (res) => {
        console.log(res.data['xmin'],res.data['ymin'],res.data['xmax'],res.data['ymax'])
        // 调用自定义的画框函数
       this.drawRect(res.data['xmin'],res.data['ymin'],res.data['xmax'],res.data['ymax'])
      },
      fail: () => {
        wx.showToast({
          title: 'Failed to process frame!',
          icon: 'none',
        });
        // 如果与服务器交互失败,清空画布
        ctx.clearRect(0,0,canvas.width,canvas.height)
      }
    });
  },

自定义的画检测框函数 

  drawRect(x1,y1,x2,y2){
    wx.createSelectorQuery().select('#myCanvas')
    .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.windowWidth
          ctx.clearRect(0,0,canvas.width,canvas.height)
          ctx.strokeStyle='red'
          ctx.lineWidth=2
          ctx.strokeRect(x1,y1,x2,y2)
        })
  },

index.js

Page({
  data: {
    windowWidth:wx.getSystemInfoSync().windowWidth*1.33,
    boxNum:'',
  },
  onLoad(){
    setInterval(this.oneProcessFrame, 800);
  },
  oneProcessFrame(){
    const context = wx.createCameraContext();
    const data={"pngData":null}
    const CamFramCall = (frame)=>{
      this.setData({
        windowWidth:frame.height/frame.width*wx.getSystemInfoSync().windowWidth*0.9
      })
      this.base64ToPNG(frame).then((pngData)=>{
        data["pngData"]=pngData
        this.interWithServer(data)
      })
      console.log('完成一次帧循环')
      listener.stop()
    }
    const listener = context.onCameraFrame(CamFramCall);
    listener.start()
  },
  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())
        })
    })
  },
  drawRect(x1,y1,x2,y2){
    wx.createSelectorQuery().select('#myCanvas')
    .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.windowWidth
          ctx.clearRect(0,0,canvas.width,canvas.height)
          ctx.strokeStyle='red'
          ctx.lineWidth=2
          ctx.strokeRect(x1,y1,x2,y2)
        })
  },
  interWithServer(data){
    const header = {
      'content-type': 'application/x-www-form-urlencoded'
    };
    wx.request({
      url: 'http://172.16.3.186:5000/predict',
      method: 'POST',
      header: header,
      data: data,
      success: (res) => {
        console.log(res.data['xmin'],res.data['ymin'],res.data['xmax'],res.data['ymax'])
        this.drawRect(res.data['xmin'],res.data['ymin'],res.data['xmax'],res.data['ymax'])
      },
      fail: () => {
        wx.showToast({
          title: 'Failed to process frame!',
          icon: 'none',
        });
        ctx.clearRect(0,0,canvas.width,canvas.height)
      }
    });
  },
  onUnload(){
  }
})

 index.wxml

<view class="c1">
  <camera class="camera" binderror="error" mode="normal" style="width: 90%; height: {{windowWidth}}px;">
    <canvas id="myCanvas" type="2d"></canvas>
  </camera>
  <view class="cla">类别:{{className}}</view>
  <view class="num">数量:{{boxNum}}</view>
  <canvas id="canvas" hidden="true" type="2d"></canvas>
</view> 

index.wxss

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

 

后端

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

这里仅作演示,不提供完整项目运行代码和依赖项文章来源地址https://www.toymoban.com/news/detail-860014.html

from deploy.infer import Detector
from PIL import Image
import cv2
import numpy as np
import io
from gevent import monkey
import base64
from flask import Flask, jsonify, request
from gevent.pywsgi import WSGIServer
monkey.patch_all()
app = Flask(__name__)

model_dir = "inferer2 fewshot\infer" # 模型路径
save_path = "output"  # 推理结果保存路径

# 推理参数设置
detector = Detector(
    model_dir,
    device='CPU',
    run_mode='paddle',
    trt_min_shape=1,
    trt_max_shape=1280,
    trt_opt_shape=640,
    trt_calib_mode=False,
    cpu_threads=1,
    enable_mkldnn=False,
    enable_mkldnn_bfloat16=False,
    output_dir=save_path,
    threshold=0.1)

// 推理函数,接收预处理后的数据,返回最终结果
def infer_start(img, threshold=0.2):
    results = detector.predict_image([img[:, :, ::-1]], visual=False)
    np_boxes=results['boxes']
    expect_boxes = (np_boxes[:, 1] > threshold) & (np_boxes[:, 0] > -1)
    np_boxes = np_boxes[expect_boxes, :]
    if len(np_boxes)>0:
        for dt in np_boxes:
            clsid, bbox, score = int(dt[0]), dt[2:], dt[1]
            xmin, ymin, xmax, ymax = bbox
            print('class_id:{:d}, confidence:{:.4f}, left_top:[{:.2f},{:.2f}],'
                'right_bottom:[{:.2f},{:.2f}]'.format(
                    int(clsid), score, xmin, ymin, xmax, ymax))

            return jsonify({"class_name":"行人","prob":float(score),"xmin":int(xmin),"ymin":int(ymin),"xmax":int(xmax),"ymax":int(ymax)})
    else:
        return jsonify({"class_name":"未检测到红火蚁","prob":0,"xmin":0,"ymin":0,"xmax":0,"ymax":0})


    
// 交互主函数
@app.route('/predict', methods=['POST'])
def predict():
    if request.method == 'POST':
        // 得到png数据,进行预处理
        img_base64 = request.form.get('frameData')
        if img_base64!='':
            img_base64 = img_base64.replace("data:image/png;base64,", "")
            img_base64 = base64.b64decode(img_base64)
            img = Image.open(io.BytesIO(img_base64))
            img=img.convert('RGB')
            img=np.array(img)
            // 调用推理函数并将结果返回
            return infer_start(img)

        else:
            return "数据为空"
        
if __name__ == '__main__':
    server = WSGIServer(('0.0.0.0', 5000), app)
    server.serve_forever()

到了这里,关于在微信小程序部署AI模型的几种方法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 微信小程序页面之间传参的几种方式

    目录 前言 第一种:url传值 url传值使用详细说明 api跳转 组件跳转 第二种:将值缓存在本地,再从本地取值 第三种:全局传值(应用实例传值) 第四种:组件传值 第五种:使用通信通道(通信通道是wx.navitageTo()独有的) 第六中:使用页面栈(只对当前页面栈中存在的页面生效

    2024年04月13日
    浏览(20)
  • 小程序(二十六)微信小程序解析富文本的几种方式

    微信小程序解析富文本html大概有两种方式(我发现的)。 两种方法,各有各的优缺点。 这个标签解析的富文本会保留你在pc端编辑的样式,也就是说,你在pc端编辑的是什么样子,小程序端显示的也是什么样子。 示例: Html Javascript: 上边这是微信小程序官方文档给出的示例

    2024年02月10日
    浏览(23)
  • 微信小程序中(设置成背景图的几种方式)

    1、使用网络图片 2、使用base64格式图片,访问图片base64编码  将背景图片使用编码base64进行转换, 网址如下: base64图片在线转换工具 - 站长工具 3、使用标签    注意有小朋友可能要用html那一套,使用background-image不适用于微信小程序 background-image: url(\\\"../images/local_image.png\\\")

    2024年04月25日
    浏览(17)
  • 微信小程序返回上级页面传参的几种方法

    在做微信小程序跳转页面,经常会遇到二级页面要返回上级页面,并且需要携带参数,wx.navigateTo()跳转大家都知道直接在url上面拼接参数,之后再二级页面onLoad(options)里获取,但是返回页面wx.navigateBack()不可以那样做,所以就可以用以下几种方式来做,具体看个人业务适合用

    2024年02月08日
    浏览(36)
  • 微信小程序wxss定位/选择/查找元素的几种方式

    wxss定位、选择、查找元素的几种方式与css类似,下面介绍常用的几种: 选择器 样例 样例描述 .class .intro 选择所有拥有 class=\\\"intro\\\" 的组件

    2024年01月16日
    浏览(34)
  • 微信小程序--data的赋值与取值的几种方式

    赋值一定需要注意。需要setData的使用,这样页面才刷新,数据才会改变,并且分清that和this的使用 Page() 函数用来注册一个页面。接受一个 object 参数,其指定页面的初始数据、生命周期函数、事件处理函数等。其中的参数data用来设置初始数据,WXML 中的动态数据均来自对应

    2024年02月11日
    浏览(21)
  • uniapp使用threejs-miniprogram在微信小程序加载模型

    1.通过 npm 安装 2.导入小程序版本的 Three.js并创建一个与 canvas 绑定的 three.js  3.创建渲染器 4.创建场景,创建相机,渲染 5.注册GLTF加载器,加载模型添加到场景 threejs-miniprogram/example/loaders/gltf-loader.js at master · wechat-miniprogram/threejs-miniprogram · GitHub 下载 gltf-loader.js 注册gltf-loader 加

    2024年02月08日
    浏览(22)
  • 微信小程序3D,使用Three.js在微信小程序中展示gltf模型,使用VisionKit展示AR能力

    本仓库只开源gltf模型展示技术,技术好的朋友有这些代码就能帮助你解决很多问题了 如需要完整项目(基于若依框架开发的后端,AR能力前端)需另外付费赞助, 联系方式:QQ 790002517 微信公众号:时不待我 https://github.com/zzy-life/Wechat3D Three.js Three.js is a JavaScript 3D library. thr

    2024年02月09日
    浏览(24)
  • 微信小程序和H5之间相互跳转的几种情况

    直接通过web-view内嵌的方式,有且只有这一种方式。 H5内嵌在小程序的web-view中,想要打开小程序自身的页面,可通过 wx.miniProgram.navigateTo 方法,参考链接:web-view | 微信开放文档 (qq.com) H5在非小程序环境中,微信浏览器或者手机自带浏览器打开时,想要跳转到小程序,可通过

    2024年02月11日
    浏览(26)
  • 微信小程序中,将一张图设置成背景图的几种方式

    三种方法实现 1、使用网络图片 2、使用base64格式图片,访问图片base64编码  将背景图片使用编码base64进行转换, 网址如下: base64图片在线转换工具 - 站长工具 3、使用标签 注意有小朋友可能要用html那一套,使用background-image不适用于微信小程序 background-image: url(\\\"../images/loc

    2024年02月11日
    浏览(27)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包