Hi头像

Hi头像

  • 网站首页
  • 技术博客
  • 教程小册

›云开发-公众号外发

标题

  • 《从0到1开发一个智能头像识别小程序》

1-前言

  • 1.1 产品需求设计、页面流程图及项目规划
  • 1.2 技术选型的依据

2-项目配置

  • 2.1 小程序开启云开发模式
  • 2.2 人脸五官分析的环境配置

3-知识储备

  • 3.1 图片裁剪、模糊、保存、分享的技巧
  • 3.2 Canvas绘制技巧,包含小程序与 Web 端
  • 3.2 云开发图像处理的使用方法

4-实战开发

  • 4.1.1 上篇:借助云存储和图像处理扩展人脸智能裁剪
  • 4.1.2 下篇:图像识别流程——图片裁剪、图片压缩、base64转换
  • 4.2 云开发必备——图像安全审核
  • 4.3 通过五官分析实现为人脸佩戴贴纸
  • 4.4 人脸贴纸移动技巧
  • 4.5 头像绘制和图片保存弹窗
  • 4.6【未写】海报分享页面
  • 4.7 CMS扩展实战-节日主题贴纸放在云开发
  • 4.8 【第二期】个人中心的制作
  • 4.9【第二期】小程序与Web端的账户体系摸索

云开发-公众号外发

  • 图片识别加速,从10秒变为1秒,是怎么做到的呢?
  • 简单几步,让微信小程序变身 H5 网页
  • 【草稿】借助云存储,让大图识别转换真正加速
  • 云开发CMS实战-Hi头像节日主题贴纸大升级
  • 小程序云开发环境部署静态网站托管的实战
  • Web 云开发实战之盲水印添加工具(核心版)
  • Web 云开发实战之盲水印添加工具(步骤版)

更多探索

  • 【草稿】深色模式在Web端的适配技巧,附带小程序侧的思考
  • 【草稿】小程序框架:Taro 2.0与Taro 3.0的核心分析

Web 云开发实战之盲水印添加工具(步骤版)

图像盲水印将水印信息以不可见的形式添加到原图信息中,您可对疑似被盗取的资源进行盲水印提取,验证图片归属。

!如果您在 web 网站使用该扩展,请先在 云开发控制台 将网站域名添加为当前环境的安全域名。其他要求如下:

  • 使用盲水印功能,水印图的宽高不得超过原图的1/8,超过就会报 Invalid Frame Size的错误。
  • 为保证盲水印效果,水印图请选用黑底白色图片。
  • 数据万象为每个账户提供每月3000张的免费体验额度,超出后将正常计费。未使用额度不会累积至下一月。
  • 文字盲水印当前支持数字[0 - 9]及英文大小写[A - Z,a - z]。
  • 盲水印可抵抗裁剪、涂抹、变色等多种图片盗取攻击,防盗效果与原图大小及攻击程度相关。

适用场景

鉴权追责-半盲水印

您可对图片资源增加半盲水印,在发现恶意攻击方盗取您的资源后将疑似被盗取图取回,并与相应原图进行盲水印提取操作,若能够得到有效水印图即可证明资源归属。

上传查重-全盲水印

为解决部分用户使用其他用户资源重复上传相同信息的问题(如房产图、汽车图、商品图等),您可在用户上传图片资源前先进行全盲水印提取,若提取到水印图信息则证明该图片来自之前已有资源,并进行相应操作(如提醒用户请勿重复上传资源);若不存在全盲水印则添加全盲水印,保护图片资源不被其他用户下载后重复上传。

资源防泄露-文字盲水印

对于内部分享的图片资源,您可通过文字盲水印将访问方的信息在请求图片时添加至图片中,当资源泄露后可通过流传出的资源图提取出盲水印,进而得到泄露方信息。

创建云开发环境

3min

一、新建【按量计费云开发环境】

我这里使用的是小程序云开发环境,将其开通了按量付费模式。

进入腾讯云云开发控制台-创建环境,选择按量计费环境,环境名称可以自定义设置。如果已有按量计费环境则可以跳到下一步。

二、开通静态网站托管服务

进入进入静态网站控制页,选择刚才创建好的环境,开通静态网站托管服务。

三、开启匿名登录

进入环境设置页-登录授权的登录方式中,勾选匿名登录

四、将云存储权限暂时放开

因为只是用了匿名登录,而尚未使用自定义登录或其他Web端登录方式,此处需要将云存储的权限放开一些。

位置:云存储 => 权限控制

{
  "read": true,
  "write": true
}

下载并部署源码

10min

一、下载源码

访问Hi头像 Github仓库,下载源码到本地。源码项目目录如下:

  • project.config.json 小程序项目目录,若Web云开发,可以换一种方式引入
  • taro/ 项目基于 Taro 构建
    • src/
      • app.js 全局配置文件,项目入口
      • pages/
        • image-watermark 盲水印添加工具
  • cloud/functions/
    • image-watermark 盲水印的添加与识别功能

项目简要说明 本项目名为Hi头像,为小程序与 Web 端可以同时运行的项目,依托于Taro 多端架构。如果你想用纯粹的 React 来运行项目,可以将项目配置与页面组件进行简要修改。

二、本地运行

将项目webviews/index.html以http的形式运行,可使用IDE工具vscode,hbuilder。在浏览器的地址栏中确定url地址,比如例子中,域名地址为127.0.0.1:10086

# 打开 Taro 文件夹
cd taro

# 安装项目依赖
npm i

# 按照下面的步骤修改云函数与前台(Web或小程序)的环境id

# 启动Taro Web端
npm run local

四、配置本地开发的安全域名

如果想在本地开发,必须要在云开发中配置本地的安全域名才能够正常调试开发。 进入环境设置页-安全配置,配置WEB安全域名,在这里以 127.0.0.1:10086举例,请按照自己的实际域名配置。

五、填写云开发环境ID到项目中

Web 云开发运行方案

因为我的项目所用的云环境是从小程序云开发中生成的,而为了让使用Web云开发的同学能运行起 Web 云开发,我这里放上 TCloudBase/WEB-2048 的环境 id 配置方案

云开发是通过环境ID来判定与特定环境进行数据通信的,所以在项目中要配置所有的相关环境ID为自己的ID。(建议熟练后,使用配置文件形式来配置)

  • 进入环境总览页,复制获取云开发环境ID。
  • 打开项目目录,将以下文件中标注有【云开发环境ID】处替换成自己的云开发环境ID
    • cloudfunctions/app/index.js 第4行

    • cloudbaserc.js 第2行

    • webviews/asset下的cloud.js文件,第2行

小程序云开发运行方案

// cloud/functions/image-watermark
tcb.init({
  env: 'env-id'
})
// Web 云开发SDK
import tcb from 'tcb-js-sdk'

componentWillMount() {
  // 小程序侧环境配置
  if (process.env.TARO_ENV === 'weapp') {
    Taro.cloud.init({
      env: 'env-id',
      traceUser: true
    })

  } else if (process.env.TARO_ENV === 'h5') {

    Taro.cloud = tcb.init({
      env: 'env-id',
      traceUser: true
    })
    // console.log('登录云开发成功!')
    Taro.cloud.auth().signInAnonymously().then(() => {
      console.log('匿名登录成功 :')
    }).catch(error => {
      console.log('error :', error);
    })
  }
}

水印添加

不仅在云函数中可以使用该扩展能力,也可以在客户端使用,文件读写权限策略与云存储一致,减去您额外的权限管理工作。

文字型

文字盲水印的噪声相对比较大。这个图片因为本身背景平滑,所以加了水印之后的噪声比较突出。文字盲水印,比较适合非纯色背景的图像。

原图

加了水印的图片

半盲型

  • 宽高不能超过 640x640,在使用时最好提醒一下
  • 水印图片的宽高不要超过原图的 1/8,并且是黑底白字。
  • 我这里用的是宽度为40的图片,因为半盲水印最大宽为640,那这个水印宽度为40会比较合适

水印图

全盲型

  • 水印图片的宽高不要超过原图的 1/8,并且是黑底白字
  • 我这里用的是宽度为100的图片,原图高为1200

水印图

加了水印的图片

云函数核心代码

  • fileID 原图FileID
  • waterType 水印类型,1为半盲水印,2为全盲水印,3为文字型水印
  • waterText,水印文字
  • waterFileID 水印图片,宽高必须小于原图宽高的小边的 1/8
async function imageWatetMark(event) {
  const { fileID, waterType = 3, waterText = '', waterFileID = ''} = event

  if (!fileID) {
    console.log('请设置fileID :')
    return
  }

  try {
    const { cloudPath, cloudEnvPath } = getImagePath(fileID)

    let rule = {
      mode: 3,
      type: waterType,
    }

    if (waterType === 3) {
      rule.text = waterText // 支持数字[0 - 9]及英文大小写[A - Z,a - z]
    } else {
      rule.image = getImagePath(waterFileID).cloudPath
    }
    const opts = {
      rules:
        [
          {
            // 处理结果的文件路径,如以’/’开头,则存入指定文件夹中,否则,存入原图文件存储的同目录
            fileid: '/watermark' + cloudPath,
            rule
          }
        ]
    }

    console.log('imageWatetMark rule : ', rule);

    const res = await tcb.invokeExtension('CloudInfinite', {
      action: 'WaterMark',
      cloudPath: cloudPath, //需要分析的图像的绝对路径
      operations: opts
    })

    let data = getResCode(res)
    let result = getResultInfo(data, cloudEnvPath)
    return {
      data: result,
      status: 0,
      message: '',
      time: new Date()
    }
  } catch (error) {
    console.log('error :', error)
    return {
      data: '',
      status: -30002,
      message: JSON.stringify(error),
      time: new Date()
    }
  }
}

前台(Web 或 小程序)核心代码

onGenerateImage = async () => {
  const { originFileID, waterType, waterText, waterFileID } = this.state

  // 前端校验
  if (waterType === 3 && !waterText) {
    this.onShowToast('请输入水印文字')
    return
  } else if (waterType !== 3 && !waterFileID) {
    this.onShowToast('请选择水印图片')
    return
  }

  try {
    Taro.showLoading({
      title: '图片生成中'
    })

    // 设置云函数参数
    let tempState = {
      fileID: originFileID,
      waterType,
    }

    if (waterType === 3) {
      tempState.waterText = waterText
    } else {
      tempState.waterFileID = waterFileID
    }

    // 图片添加水印
    const { fileID, fileUrl } = await cloudCallFunction({
      name: 'image-watermark',
      data: tempState
    })

    Taro.hideLoading()

    console.log('fileID :>> ', fileID);
    this.setState({
      isWaterChanged: false,
      savedFileID: fileID,
      savedUrl: fileUrl
    })

  } catch (error) {
    Taro.hideLoading()
    console.log('error :>> ', error);
    const { message } = error || {}
    this.onShowToast(message || JSON.stringify(error))
  }
}

水印提取

文字型

文字型盲水印,我实践下来识别结果还OK,可能存在识别几个字母的一两个,依据图片和水印情况而定。

识别出的水印

半盲型

  • 生成后的图片
  • 规则图片为未带盲水印的原图图片地址

加了水印的图片

识别出的水印

全盲型

  • 生成后的图片
  • 规则图片为已经添加盲水印的图地址

加了水印的图片

识别出的水印

云函数核心代码

  • fileID 添加水印后的图片 FileID
  • waterType 水印类型,1为半盲水印,2为全盲水印,3为文字型水印
  • waterText,水印文字,在文字型水印中用到
  • originFileID 半盲水印中所用到的原图
  • savedFileID 全盲水印中添加了水印的图片
async function imageWatetMarkParse(event) {
  const { fileID, waterType = 3, waterText = '', originFileID  = '', savedFileID = '' } = event
  if (!fileID) {
    console.log('请设置fileID :')
    return
  }

  try {
    const { cloudPath, cloudEnvPath } = getImagePath(fileID)

    let rule = {
      mode: 4,
      type: waterType,
    }

    if (waterType === 3) {
      rule.text = waterText // 支持数字[0 - 9]及英文大小写[A - Z,a - z]
    } else if (waterType === 1) {
      rule.image = getImagePath(originFileID).cloudPath
    } else if (waterType === 2) {
      rule.image = getImagePath(savedFileID).cloudPath
    }

    const opts = {
      rules:
        [
          {
            // 处理结果的文件路径,如以’/’开头,则存入指定文件夹中,否则,存入原图文件存储的同目录
            fileid: '/watermark/parse' + cloudPath,
            rule
          }
        ]
    }

    console.log('imageWatetMarkParse rule :>> ', rule);

    const res = await tcb.invokeExtension('CloudInfinite', {
      action: 'WaterMark',
      cloudPath: cloudPath, //需要分析的图像的绝对路径
      operations: opts
    })

    
    let data = getResCode(res)
    
    let result = getResultInfo(data, cloudEnvPath)

    console.log('fileID :>> ', result.fileID);
    
    return {
      data: result,
      status: 0,
      message: '',
      time: new Date()
    }
  } catch (error) {
    console.log('error :', error)
    return {
      data: '',
      status: -30003,
      message: JSON.stringify(error),
      time: new Date()
    }
  }
}

前台(Web 或小程序)核心代码

onLookCheck = async () => {
  const { savedFileID, waterType, waterText, originFileID, isWaterChanged } = this.state

  if (isWaterChanged) {
    this.onShowToast('请重新生成水印图片')
    return 
  }

  try {
    Taro.showLoading({
      title: '图片生成中'
    })

    let tempState = {
      fileID: savedFileID,
      waterType,
    }

    if (waterType === 3) {
      // 文字水印
      tempState.waterText = waterText
    } else if (waterType == 1) {
      // 全盲水印
      tempState.originFileID = originFileID
    }else if (waterType == 2) {
      // 全盲水印
      tempState.savedFileID = savedFileID
    }

    const { fileID, fileUrl } = await cloudCallFunction({
      name: 'image-watermark',
      data: {
        type: 'parse',
        ...tempState
      }
    })

    Taro.hideLoading()

    console.log('onLookCheck fileID :>> ', fileID);
    this.setState({
      isWaterChanged: false,
      waterSeeFileID: fileID,
      waterSeeUrl: fileUrl
    }, () => {
      this.posterRef.onShowPoster()
    })

  } catch (error) {
    Taro.hideLoading()
    console.log('error :>> ', error);
    const { message } = error || {}
    this.onShowToast(message || JSON.stringify(error))
  }
  
}

更多实战代码技巧

快速获取图片所在的环境前缀前缀和绝对位置

const cloudPrefix = 'cloud://'
const getImagePath = (fileID) => {
  let imgID = fileID.replace(cloudPrefix, '')
  let index = imgID.indexOf('/')
  let cloudPath = imgID.substr(index)
  let cloudEnvPath = imgID.substr(0, index)

  return {
    cloudPath,
    cloudEnvPath
  }
}

统一处理接口请求中的外侧包装,

{
  "statusCode": 200,
  "data": {
    "UploadResult": {}
  }
}
const getResCode = (res) => {
  if (res.statusCode === 200) {
    let result = res.data
    console.log('result :', result);
    if (result.UploadResult) {
      const finalResult = result.UploadResult
      if (Object.keys(finalResult).length === 0) return finalResult || {} // 某些接口判断返回data字段是否是空对象的逻辑
      return finalResult
    } else {
      throw result
    }
  } else {
    throw res.data
  }
}

将水印添加和水印的结果进行统一处理

const getResultInfo = (data, cloudEnvPath) => {
  const { ProcessResults } = data

  let { Key, Width, Height, Location } = ProcessResults.Object
  return {
    // fileID 水印添加时为添加了水印的图片,水印提取时为提取出的水印图
    fileID: cloudPrefix + cloudEnvPath + '/' + Key,
    // fileUrl 为 fileID 对应的Url地址。因为Web端图片 src 不支持 cloudID
    fileUrl: 'https://' + Location,
    width: Width,
    height: Height
  }
}
← Web 云开发实战之盲水印添加工具(核心版)【草稿】深色模式在Web端的适配技巧,附带小程序侧的思考 →
  • 适用场景
    • 鉴权追责-半盲水印
    • 上传查重-全盲水印
    • 资源防泄露-文字盲水印
  • 创建云开发环境
    • 一、新建【按量计费云开发环境】
    • 二、开通静态网站托管服务
    • 三、开启匿名登录
    • 四、将云存储权限暂时放开
  • 下载并部署源码
    • 一、下载源码
    • 二、本地运行
    • 四、配置本地开发的安全域名
    • 五、填写云开发环境ID到项目中
  • 水印添加
    • 文字型
    • 半盲型
    • 全盲型
    • 云函数核心代码
    • 前台(Web 或 小程序)核心代码
  • 水印提取
    • 文字型
    • 半盲型
    • 全盲型
    • 云函数核心代码
    • 前台(Web 或小程序)核心代码
  • 更多实战代码技巧
    • 快速获取图片所在的环境前缀前缀和绝对位置
    • 统一处理接口请求中的外侧包装,
    • 将水印添加和水印的结果进行统一处理
Hi头像
文档
开篇介绍
相关资源
云开发 CloudBaseTaro
关于
联系我们GitHub主页Star
Copyright © 2020. All Rights Reserved.
沪ICP备2022023998号