# 智能裁剪
# 体验 demo
选择克隆或下载代码到小程序端 IDE, 更新项目 project.config.json
中的 appid
字段为您的小程序 ID,在当前环境中部署部署 (opens new window) AI 人脸特征分析与检测
扩展能力,即可体验智能裁剪小程序。亦可扫码体验线上版本。
# 克隆
- 确保您已安装 git 客户端。
- 在命令行中通过以下指令,克隆 demo 仓库:
git clone https://github.com/TencentCloudBase/Cloudbase-Examples.git--branch feature/keylessCall
- 进入 clone 下来的 tcb-demo-ai 目录可见项目代码
cd ./tcb-demo-ai
# 下载
若无 git 客户端,或其他原因,可以选择手动下载方式:点击下载 (opens new window)项目代码包,解压到您喜欢的目录。
# 开发
在小程序 IDE 中打开项目,注意更新 project.config.json
中的 appid
字段为您的小程序 ID。
# 目录说明
/client/pages/index
- 首页导航目录/client/pages/face-detect/clip
- 智能裁剪页面入口/client/libs/
- 依赖库相关/client/libs/runtime.js
- 在某页面引用后,可在该页面使用async/await
语法,主要用于兼容iOS
手机
/client/libs/weui.wxss
- weui 样式文件,在人脸识别组件中有使用,可选。
/client/libs/tcb-services-mp-sdk
- 小程序中使用的tcb-services-mp-sdk
,封装 tcb 相关公共能力及方法,可选。
# 上传图片
clip/index.wxml #8 (opens new window) 中上传按钮绑定
tap
事件处理函数handleUploadTap
,点击按钮将触发上传图片操作。<view class="button-container"> <button type="primary" bindtap="handleUploadTap">上传图片</button> </view>
在 clip/index.js #21 (opens new window) 中实现
handleUploadTap
方法。通过调用wx.chooseImage
选择需要上传的图片,获取到图片对象后,使用云开发存储能力,调用 wx.cloud.uploadFile (opens new window) 将图片上传到云端,在成功回调中获取云端文件对象,拿到fileID
存储在组件data
中,同时存储tempFilePaths
为temUrl
待用,调用this.recognize()
进行图片分析(下一步将实现该方法)。handleUploadTap() { // 通过微信 API,选择上传的图片 wx.chooseImage({ success: dRes => { wx.showLoading({ title: "上传中" }); const fileName = dRes.tempFilePaths[0]; const dotPosition = fileName.lastIndexOf("."); const extension = fileName.slice(dotPosition); const cloudPath = `${Date.now()}-${Math.floor( Math.random(0, 1) * 10000 )}${extension}`; // 云开发上传图片到云端 wx.cloud.uploadFile({ cloudPath, filePath: dRes.tempFilePaths[0], // 上传成功回调 success: res => { console.log(res); // 将 fileID 存在组件 data 上 this.setData( { temUrl: dRes.tempFilePaths[0] fileID: res.fileID, hasUploaded: true }, () => { // 图像识别方法,将在下一步实现 this.recognize(); wx.hideLoading(); } ); }, // 上传失败回调,可用于捕获错误,进行提示 fail: () => { wx.hideLoading(); wx.showToast({ title: "上传失败", icon: "none" }); } }); } }); }
# 图片分析
在上一步,我们将一张本地图片上传到云端,并且获取到了云端 fileID,接下来我们将调用云开发扩展能力解决方案 - AI 人脸特征分析与检测
,进行图片分析。首先请确保在云开发中部署了 AI 人脸特征分析与检测
能力,详见 AI 人脸特征分析与检测使用指引 (opens new window)。
首先在 clip/index.js (opens new window) 文件头引入依赖
/client/libs/tcb-services-mp-sdk
与/client/libs/runtime.js
。import regeneratorRuntime from "../../../libs/runtime"; import TcbService from "../../../libs/tcb-service-mp-sdk/index"; const tcbService = new TcbService();
之后实现
recognize
方法。通过tcbService.callService
方法,传入之前存储于组件 data 中的fileID
,调用AI 人脸特征分析与检测
能力,获取图片识别结果。this.getFaceRect
对识别结果简单处理,获取原图像宽高、人脸相对于图片的位置大小,传递给this.clip
方法进行裁剪。async recognize() { wx.showLoading({ title: "识别中", icon: "none" }); try { let result = await tcbService.callService({ service: "ai", action: "tcbService-ai-detectFace", data: { FileID: this.data.fileID } }); /** 或可简单的直接调用云函数 let result = await wx.cloud.callFunction({ name: 'tcbService-ai-detectFace', data: { FileID: this.data.fileID } }); **/ wx.hideLoading(); if (!result.code && result.data) { // 图像识别成功,结果对象 result.data let imgInfo = this.getFaceRect(result.data); this.clip(this.data.temUrl, imgInfo); } else { // 识别失败,抛出错误进行错误处理 throw result; } } catch (e) { wx.hideLoading(); wx.showToast({ title: "识别失败", icon: "none" }); console.log(e); } }, getFaceRect(res) { const { ImageWidth, ImageHeight, FaceInfos } = res; let face = FaceInfos[0]; return { imageWidth: ImageWidth, imageHeight: ImageHeight, rectX: face.X / ImageWidth, rectY: face.Y / ImageHeight, rectWidth: face.Width / ImageWidth, rectHeight: face.Height / ImageHeight }; }
# 图像裁剪
在 clip/index.wxml #11 (opens new window) 中添加画布容器
<view class="preview-container">
<canvas
wx:for="{{clipSizes}}"
wx:for-item="size"
wx:key="{{size[0]/size[1]}}"
style="width: {{size[0]}}rpx; height: {{size[1]}}rpx; background-image: url({{thumb}})"
canvas-id="canvas-{{size[0]/size[1]}}"
class="canvas"
></canvas>
</view>
在 clip/index.js #101 (opens new window) 实现 clip
方法,使用 canvas (opens new window),根据识别信息进行裁剪。基本裁剪策略为:
- 原图等比缩放至接近目标大小,比较原图与目标大小的宽高比,若原图像宽高比较大,则保持高边对齐,缩放至目标大小,反之保持宽边对齐进行缩放。缩放后裁剪窗口可在宽/高方向上进行滑动,选择合适的裁剪位置。
- 根据人脸位置与大小,计算值人脸中心点位置,将人脸中心点在宽/高方向上与裁剪窗口中心对齐,若裁剪窗口超出缩放后图像,则进行平移,直至裁剪窗口刚好落在图像内。
- 使用 CanvasContext.drawImage (opens new window) 将裁剪窗口内的图像绘制于画布中,获取裁剪后图像。(此处需要注意,canvas 的绘制单位为 px,无法根据 rpx 绘制,如果画布设置使用了 rpx,需要进行转换。)
示例代码
// 示例采用了 3 种规格的裁剪,
// data.clipSizes: [[100, 100], [300, 200], [160, 90]]
clip( url, { imageWidth, imageHeight, rectX, rectWidth, rectY, rectHeight }) {
this.data.clipSizes.forEach(([clipWidth, clipHeight]) => {
let middleWidth = rectX + rectWidth / 2;
let middleHeight = rectY + rectHeight / 2;
let clipAspectRatio = clipWidth / clipHeight;
let imageAspectRatio = imageWidth / imageHeight;
let top = 0,
left = 0;
let right = 1,
bottom = 1;
if (imageAspectRatio < clipAspectRatio) {
// 宽边对齐,上下移动
let halfHeight = imageAspectRatio / clipAspectRatio / 2;
top = middleHeight - halfHeight;
bottom = middleHeight + halfHeight;
if (top < 0) {
bottom = halfHeight * 2;
top = 0;
} else if (bottom > 1) {
top = 1 - halfHeight * 2;
bottom = 1;
}
} else {
// 高边对齐,左右移动
let halfWidth = clipAspectRatio / imageAspectRatio / 2;
left = middleWidth - halfWidth;
right = middleWidth + halfWidth;
if (left < 0) {
right += -left;
left = 0;
} else if (right > 1) {
left = 1 - right + left;
right = 1;
}
}
wx.getSystemInfo({
success: function(res) {
// 获取系统屏幕可用宽度,计算缩放大小
let context = wx.createCanvasContext(`canvas-${clipAspectRatio}`);
context.drawImage(
url,
Math.floor(left * imageWidth),
Math.floor(top * imageHeight),
Math.floor((right - left) * imageWidth),
Math.floor((bottom - top) * imageHeight),
0,
0,
(res.windowWidth / 750) * clipWidth,
(res.windowWidth / 750) * clipHeight
);
context.draw(false);
}
});
});
}
编译项目,可以实现从文件上传,到人脸识别,再到图片裁剪输出的整个流程。