diff --git a/app.js b/app.js index 8958abf..38725ae 100644 --- a/app.js +++ b/app.js @@ -1,16 +1,31 @@ import updateManager from './common/updateManager'; -import * as paddlejs from '@paddlejs/paddlejs-core'; -import '@paddlejs/paddlejs-backend-webgl'; +var fetchWechat = require('fetch-wechat'); +var tf = require('@tensorflow/tfjs-core'); +var webgl = require('@tensorflow/tfjs-backend-webgl'); +var plugin = requirePlugin('tfjsPlugin'); -// const paddlejs = require('paddlejs'); -const plugin = requirePlugin("paddlejs-plugin"); - -let pdjs; App({ + globalData: { + localStorageIO: plugin.localStorageIO, + systemInfo: {} + }, onLaunch: function () { - plugin.register(paddlejs, wx); + plugin.configPlugin({ + // polyfill fetch function + fetchFunc: fetchWechat.fetchFunc(), + // inject tfjs runtime + tf, + // inject webgl backend + webgl, + // provide webgl canvas + canvas: wx.createOffscreenCanvas() + }, + true); + + const systemInfo = wx.getSystemInfoSync(); + this.globalData.systemInfo = wx.getSystemInfoSync(); }, onShow: function () { updateManager(); diff --git a/app.json b/app.json index ca88026..da29495 100644 --- a/app.json +++ b/app.json @@ -52,9 +52,9 @@ } }, "plugins": { - "paddlejs-plugin": { - "version": "2.0.1", - "provider": "wx7138a7bb793608c3" + "tfjsPlugin": { + "version": "0.2.0", + "provider": "wx6afed118d9e81df9" } } } \ No newline at end of file diff --git a/package.json b/package.json index 863026c..f601b27 100644 --- a/package.json +++ b/package.json @@ -23,8 +23,10 @@ "dayjs": "^1.9.3", "tdesign-miniprogram": "1.0.1", "tslib": "^1.11.1", - "@paddlejs/paddlejs-backend-webgl": "^1.0.7", - "@paddlejs/paddlejs-core": "^2.0.7" + "@tensorflow/tfjs-core": "3.5.0", + "@tensorflow/tfjs-converter": "3.5.0", + "@tensorflow/tfjs-backend-webgl": "3.5.0", + "fetch-wechat": "0.0.3" }, "devDependencies": { "eslint": "^6.8.0", diff --git a/pages/home/camera/index.js b/pages/home/camera/index.js index a1fb089..aa840af 100644 --- a/pages/home/camera/index.js +++ b/pages/home/camera/index.js @@ -1,10 +1,6 @@ // pages/home/camera/index.js -import { - paddlejs -} from '@paddlejs/paddlejs-core'; -import { - Paddlejs -} from '../../../services/_utils/ocr'; + +import * as model from '../../../services/tf/model' Page({ @@ -13,44 +9,79 @@ Page({ */ data: { isAuth: false, - src: '' + src: '', + fps: 0, + predicting: false, + cameraContext: null, + cameraListener: null, + lastTime: 0 }, /** * 生命周期函数--监听页面加载 */ onLoad(options) { - const _this = this - wx.getSetting({ - success: res => { - if (res.authSetting['scope.camera']) { - // 用户已经授权 - _this.setData({ - isAuth: true - }) - } else { - // 用户还没有授权,向用户发起授权请求 - wx.authorize({ - scope: 'scope.camera', - success() { // 用户同意授权 - _this.setData({ - isAuth: true - }) - }, - fail() { // 用户不同意授权 - _this.openSetting().then(res => { - _this.setData({ - isAuth: true - }) - }) - } - }) - } - }, - fail: res => { - console.log('获取用户授权信息失败') - } + this.setData({ + lastTime: Date.now() }) + this.initModel(); + }, + + initModel: async function () { + this.showLoadingToast(); + + await model.load(); + + this.hideLoadingToast(); + + if (!model.isReady()) { + wx.showToast({ + title: '网络连接异常', + icon: 'none' + }); + } + }, + + showLoadingToast: function () { + wx.showLoading({ + title: '拼命加载模型', + }); + }, + + hideLoadingToast: function () { + wx.hideLoading() + }, + + openCamera() { + this.setData({ + cameraContext: wx.createCameraContext() + }) + + this.setData({ + cameraListener: this.data.cameraContext.onCameraFrame(frame => { + this.executePredict(frame) + }) + }) + this.data.cameraListener.start(); + }, + + executePredict(frame) { + if (!this.data.predicting && model.isReady()) { + this.setData({ + predicting: true + }, async () => { + const now = Date.now() + // model.predict(frame) + // const predictionResults = await model.classify(frame) + // console.log(now - this.data.lastTime); + model.predict(frame) + this.setData({ + predicting: false, + fps: (1000 / (now - this.data.lastTime)).toFixed(2), + lastTime: now + }) + }) + } }, openSetting() { @@ -86,81 +117,43 @@ Page({ return promise; }, - takePhoto() { - console.log("开始识别图片"); - const ctx = wx.createCameraContext() - ctx.takePhoto({ - quality: 'high', - success: (res) => { - console.log(res); - this.setData({ - src: res.tempImagePath - }) - // Paddlejs.predict(res.tempImagePath).then(res => { - // console.log(res); - // }) - var pic0 = new Image(); - pic0.src = res.tempImagePath; - const this_ = this - pic0.onload = () => { - var img0 = tf.browser.fromPixels(pic0); - this_.predict(img0); - } - - // 获取到图片的像素信息 - // wx.getImageInfo({ - // src: res.tempImagePath, - // success: (imgInfo) => { - // const { - // width, - // height, - // path - // } = imgInfo; - - // const canvasId = 'myCanvas'; - // const me = this; - // // 获取页面中的canvas上下文,tips:canvas设置的宽高要大于选择的图片宽高,canvas位置可以绝对定位到视口不可以见 - // let ctx = canvas.getContext(canvasId); - // ctx.drawImage(path, 0, 0, width, height); - // ctx.draw(false, () => { - // // API 1.9.0 获取图像数据 - // wx.canvasGetImageData({ - // canvasId: canvasId, - // x: 0, - // y: 0, - // width: width, - // height: height, - // success(res) { - // console.log(res); - // me.predict({ - // data: res.data, - // width: width, - // height: height - // }); - // } - // }); - // }); - // } - // }); - } - }) - }, - - predict(imgObj) { - // 4. 在线预测计算 - const me = this; - Paddlejs.predict(imgObj, function (data) { - // 5. 对预测结果进行后处理 - const maxItem = pdjs.utils.getMaxItem(data); - console.log(maxItem); - }); - }, - /** * 生命周期函数--监听页面初次渲染完成 */ onReady() { - + const _this = this + wx.getSetting({ + success: res => { + if (res.authSetting['scope.camera']) { + // 用户已经授权 + _this.setData({ + isAuth: true + }) + _this.openCamera() + } else { + // 用户还没有授权,向用户发起授权请求 + wx.authorize({ + scope: 'scope.camera', + success() { // 用户同意授权 + _this.setData({ + isAuth: true + }) + _this.openCamera() + }, + fail() { // 用户不同意授权 + _this.openSetting().then(res => { + _this.setData({ + isAuth: false + }) + }) + } + }) + } + }, + fail: res => { + console.log('获取用户授权信息失败') + } + }) }, /** @@ -181,7 +174,7 @@ Page({ * 生命周期函数--监听页面卸载 */ onUnload() { - + this.data.cameraListener.stop(); }, /** diff --git a/pages/home/camera/index.wxml b/pages/home/camera/index.wxml index 6db6e0f..fe3fa48 100644 --- a/pages/home/camera/index.wxml +++ b/pages/home/camera/index.wxml @@ -2,5 +2,5 @@ - - \ No newline at end of file + FPS: {{fps}} + \ No newline at end of file diff --git a/project.config.json b/project.config.json index c9c4f6d..503980b 100644 --- a/project.config.json +++ b/project.config.json @@ -43,7 +43,8 @@ "showES6CompileOption": false, "useCompilerPlugins": false, "ignoreUploadUnusedFiles": true, - "useStaticServer": true + "useStaticServer": true, + "condition": false }, "compileType": "miniprogram", "libVersion": "2.23.1", diff --git a/services/_utils/ocr.js b/services/_utils/ocr.js deleted file mode 100644 index 6aa168f..0000000 --- a/services/_utils/ocr.js +++ /dev/null @@ -1,21 +0,0 @@ -import * as paddlejs from '@paddlejs/paddlejs-core'; -import '@paddlejs/paddlejs-backend-webgl'; - -const plugin = requirePlugin("paddlejs-plugin"); -plugin.register(paddlejs, wx); - -export const Paddlejs = new paddlejs.Runner({ - modelPath: '/resources/paddle.json', - feedShape: { - fw: 224, - fh: 224 - }, - fill: '#fff', - targetSize: { - height: 224, - width: 224 - }, - mean: [0.485, 0.456, 0.406], - std: [0.229, 0.224, 0.225], - // needPreheat: true -}) \ No newline at end of file diff --git a/services/tf/model.js b/services/tf/model.js new file mode 100644 index 0000000..b5f016c --- /dev/null +++ b/services/tf/model.js @@ -0,0 +1,67 @@ +import * as tfc from '@tensorflow/tfjs-converter'; +import * as tf from '@tensorflow/tfjs-core'; +import '@tensorflow/tfjs-backend-webgl'; + +const LOCAL_STORAGE_KEY = 'mobilenet_model'; +// const MODEL_URL = 'https://shao5.net/static/model/model.json'; +const MODEL_URL = 'https://webplus-cn-hangzhou-s-603871eef968dd14ced82ed5.oss-cn-hangzhou.aliyuncs.com/hextech/static/paddle/model.json'; + +let model = tfc.GraphModel; +const app = getApp(); + +export async function load() { + + // const localStorageHandler = getApp().globalData.localStorageIO(LOCAL_STORAGE_KEY); + // try { + // model = await tfc.loadGraphModel(localStorageHandler); + // } catch (e) { + // model = + // await tfc.loadGraphModel(MODEL_URL); + // model.save(localStorageHandler); + // } + model = await tfc.loadGraphModel(MODEL_URL); + console.log(model); +} + +export const isReady = () => { + return !!model; +}; + + +const getFrameSliceOptions = (frameWidth, frameHeight, displayWidth, displayHeight) => { + let result = { + start: [0, 0, 0], + size: [-1, -1, 3] + }; + + const ratio = displayHeight / displayWidth; + + if (ratio > frameHeight / frameWidth) { + result.start = [0, Math.ceil((frameWidth - Math.ceil(frameHeight / ratio)) / 2), 0]; + result.size = [-1, Math.ceil(frameHeight / ratio), 3]; + } else { + result.start = [Math.ceil((frameHeight - Math.floor(ratio * frameWidth)) / 2), 0, 0]; + result.size = [Math.ceil(ratio * frameWidth), -1, 3]; + } + + return result; +} + +export const predict = async (frame) => { + const temp = tf.browser.fromPixels({ + data: new Uint8Array(frame.data), + width: frame.width, + height: frame.height, + }, 4); + const sliceOptions = getFrameSliceOptions(frame.width, frame.height, app.globalData.systemInfo.windowWidth, app.globalData.systemInfo.windowWidth) + + const pixels = await tf.tidy(() => { + return tf.image.resizeBilinear(tf.slice(temp, sliceOptions.start, sliceOptions.size), [224, 224]); + }); + const tensor = tf.reshape(pixels, [-1, 224, 224, 3]); + + const res = model.execute(tensor) + console.log(res); + temp.dispose(); + pixels.dispose(); +} \ No newline at end of file