更新记录

1.0.3(2026-03-09)

动作检测直接基于关键点几何特征(角度、距离),删除复杂的置信度评分

1.0.2(2026-03-09)

优化运动识别

1.0.1(2026-03-08)

根据追踪点优化识别准确

查看更多

平台兼容性

uni-app(4.81)

Vue2 Vue3 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
- - × 6.0 × -
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟
- - - - - - - - - - - -

rd-ai-vision AI视觉识别插件

高性能 AI 视觉识别 UTS 插件(仅支持 Android 端),集成 MediaPipe AI 能力,支持手势识别、姿态检测、运动计数、面部检测等功能。

技术栈

技术 说明
MediaPipe Google 开源机器学习框架,用于人体姿态、手势、面部检测
CameraX Android Jetpack 相机库,提供高性能相机预览和图像分析
Kotlin 原生 Android 开发语言
UTS uni-app 跨平台原生开发语言

MediaPipe 模型

模型 功能 关键点数量
Pose Landmarker 人体姿态检测 33 个关键点
Hand Landmarker 手势识别 每只手 21 个关键点
Face Mesh 面部网格检测 478 个关键点

功能特性

基础功能

  • 自定义相机预览(支持多种缩放模式)
  • 前后摄像头切换
  • 闪光灯控制
  • 关键帧截图

AI 识别功能

功能 说明
手势识别 支持识别 OK、指向、剪刀手、摇滚、竖大拇指、张开手掌、握拳等手势
姿态检测 实时检测 33 个人体关键点,提供 3D 坐标和世界坐标
运动计数 自动识别深蹲、俯卧撑、开合跳、弓步、高抬腿、踢腿、卷腹、臀桥、平板支撑、举手、侧平举等动作并计数
面部检测 检测 478 个面部关键点,可用于面部表情分析

安装

安装插件

uni_modules/rd-ai-vision 目录复制到项目的 uni_modules 目录下。

权限要求

Android

权限 说明
CAMERA 相机权限
RECORD_AUDIO 麦克风权限(视频录制)
READ_EXTERNAL_STORAGE 读取存储权限
WRITE_EXTERNAL_STORAGE 写入存储权限

组件使用示例

基础用法

<template>
  <view class="container">
    <!-- AI 视觉组件 -->
    <rd-ai-vision
      ref="aiVision"
      class="camera-preview"
      cameraPosition="front"
      :scaleType="3"
      @onCameraReady="onCameraReady"
      @onError="onError"
      @onGestureDetected="onGestureDetected"
      @onPoseDetected="onPoseDetected"
      @onMotionRecognized="onMotionRecognized"
      @onFaceDetected="onFaceDetected"
    />
  </view>
</template>

<script>
export default {
  data() {
    return {
      isReady: false
    }
  },
  methods: {
    onCameraReady(e) {
      console.log('相机已就绪')
      this.isReady = true

      // 启用各种检测功能
      this.$refs.aiVision.enablePoseDetection()      // 姿态检测
      this.$refs.aiVision.enableGestureDetection()    // 手势检测
      this.$refs.aiVision.enableFaceDetection()       // 面部检测
    },

    onError(e) {
      console.error('错误:', e.detail.message)
    },

    onGestureDetected(e) {
      const gestures = e.detail.gestures
      if (gestures.length > 0) {
        console.log('检测到手势:', gestures[0].gesture, '置信度:', gestures[0].confidence)
      }
    },

    onPoseDetected(e) {
      const { landmarks, worldLandmarks } = e.detail
      console.log('检测到姿态:', landmarks.length, '个关键点')
    },

    onMotionRecognized(e) {
      const { action, confidence, details } = e.detail
      console.log('运动识别:', action, '置信度:', confidence)
      console.log('计数:', details)
    },

    onFaceDetected(e) {
      const { landmarks, score } = e.detail
      console.log('检测到面部:', landmarks.length, '个关键点')
    }
  },

  onUnload() {
    // 释放资源
    this.$refs.aiVision?.prepareForUnload()
  }
}
</script>

<style>
.container {
  flex: 1;
  background-color: #000;
}

.camera-preview {
  width: 100%;
  height: 100%;
}
</style>

完整示例(健身计数应用)

<template>
  <view class="container">
    <!-- 相机组件 -->
    <rd-ai-vision
      ref="aiVision"
      class="camera-preview"
      cameraPosition="front"
      :scaleType="3"
      @onCameraReady="onCameraReady"
      @onMotionRecognized="onMotionRecognized"
      @onGestureDetected="onGestureDetected"
    />

    <!-- 运动信息显示 -->
    <view class="motion-overlay">
      <view class="action-info">
        <text class="action-text">{{ currentAction }}</text>
        <text class="confidence-text" v-if="confidence > 0">
          置信度: {{ (confidence * 100).toFixed(0) }}%
        </text>
      </view>

      <view class="counts-grid">
        <view class="count-item">
          <text class="count-number">{{ motionCounts.squat || 0 }}</text>
          <text class="count-label">深蹲</text>
        </view>
        <view class="count-item">
          <text class="count-number">{{ motionCounts.pushUp || 0 }}</text>
          <text class="count-label">俯卧撑</text>
        </view>
        <view class="count-item">
          <text class="count-number">{{ motionCounts.jumpingJack || 0 }}</text>
          <text class="count-label">开合跳</text>
        </view>
        <view class="count-item">
          <text class="count-number">{{ motionCounts.lunge || 0 }}</text>
          <text class="count-label">弓步</text>
        </view>
      </view>

      <!-- 手势显示 -->
      <view class="gesture-info" v-if="currentGesture">
        <text class="gesture-text">手势: {{ currentGesture }}</text>
      </view>
    </view>

    <!-- 控制按钮 -->
    <view class="controls">
      <button @click="switchCamera">切换摄像头</button>
      <button @click="toggleFlash">闪光灯</button>
      <button @click="resetCounts">重置计数</button>
      <button @click="captureFrame">截图</button>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      isReady: false,
      currentAction: '准备中...',
      confidence: 0,
      currentGesture: '',
      motionCounts: {
        squat: 0,
        pushUp: 0,
        jumpingJack: 0,
        lunge: 0,
        armRaise: 0,
        highKnee: 0,
        sideRaise: 0,
        crunch: 0,
        gluteBridge: 0,
        kick: 0
      }
    }
  },

  methods: {
    onCameraReady(e) {
      console.log('相机就绪')
      this.isReady = true
      this.currentAction = '站立'

      // 启用检测
      this.$refs.aiVision.enablePoseDetection()
      this.$refs.aiVision.enableGestureDetection()
    },

    onMotionRecognized(e) {
      const { action, confidence, details } = e.detail

      if (action && action !== '无数据') {
        this.currentAction = action
        this.confidence = confidence

        // 更新计数
        if (details) {
          this.motionCounts = {
            squat: details.squatCount || this.motionCounts.squat,
            pushUp: details.pushUpCount || this.motionCounts.pushUp,
            jumpingJack: details.jumpingJackCount || this.motionCounts.jumpingJack,
            lunge: details.lungeCount || this.motionCounts.lunge,
            armRaise: details.armRaiseCount || this.motionCounts.armRaise,
            highKnee: details.highKneeCount || this.motionCounts.highKnee,
            sideRaise: details.sideRaiseCount || this.motionCounts.sideRaise,
            crunch: details.crunchCount || this.motionCounts.crunch,
            gluteBridge: details.gluteBridgeCount || this.motionCounts.gluteBridge,
            kick: details.kickCount || this.motionCounts.kick
          }
        }
      }
    },

    onGestureDetected(e) {
      const gestures = e.detail.gestures
      if (gestures && gestures.length > 0) {
        this.currentGesture = gestures[0].gesture
      } else {
        this.currentGesture = ''
      }
    },

    switchCamera() {
      this.$refs.aiVision.switchCamera()
    },

    toggleFlash() {
      const result = this.$refs.aiVision.toggleFlash()
      uni.showToast({ title: result ? '闪光灯已开启' : '闪光灯已关闭', icon: 'none' })
    },

    resetCounts() {
      this.$refs.aiVision.resetMotionCounters()
      this.motionCounts = {
        squat: 0, pushUp: 0, jumpingJack: 0, lunge: 0,
        armRaise: 0, highKnee: 0, sideRaise: 0,
        crunch: 0, gluteBridge: 0, kick: 0
      }
      uni.showToast({ title: '计数已重置', icon: 'success' })
    },

    captureFrame() {
      this.$refs.aiVision.requestKeyFrame()
    }
  },

  onUnload() {
    this.$refs.aiVision?.prepareForUnload()
  }
}
</script>

<style>
.container {
  flex: 1;
  background-color: #000;
}

.camera-preview {
  width: 100%;
  height: 100%;
  position: absolute;
}

.motion-overlay {
  position: absolute;
  top: 40rpx;
  left: 20rpx;
  right: 20rpx;
}

.action-info {
  background-color: rgba(0, 0, 0, 0.6);
  padding: 20rpx;
  border-radius: 16rpx;
  margin-bottom: 20rpx;
}

.action-text {
  color: #fff;
  font-size: 48rpx;
  font-weight: bold;
}

.confidence-text {
  color: #4CAF50;
  font-size: 28rpx;
  margin-left: 20rpx;
}

.counts-grid {
  display: flex;
  flex-wrap: wrap;
  background-color: rgba(0, 0, 0, 0.6);
  padding: 20rpx;
  border-radius: 16rpx;
}

.count-item {
  width: 25%;
  text-align: center;
  padding: 10rpx;
}

.count-number {
  color: #4CAF50;
  font-size: 48rpx;
  font-weight: bold;
  display: block;
}

.count-label {
  color: #aaa;
  font-size: 24rpx;
}

.gesture-info {
  background-color: rgba(76, 175, 80, 0.8);
  padding: 16rpx 32rpx;
  border-radius: 30rpx;
  margin-top: 20rpx;
  align-self: flex-start;
}

.gesture-text {
  color: #fff;
  font-size: 32rpx;
}

.controls {
  position: absolute;
  bottom: 40rpx;
  left: 20rpx;
  right: 20rpx;
  display: flex;
  justify-content: space-around;
}

.controls button {
  background-color: rgba(255, 255, 255, 0.2);
  color: #fff;
  font-size: 28rpx;
  padding: 20rpx 30rpx;
  border-radius: 40rpx;
}
</style>

API 文档

Props 属性

属性 类型 默认值 说明
cameraPosition String 'back' 摄像头位置:'front' 前置 / 'back' 后置
scaleType Number 3 预览缩放模式
scaleType 取值: 模式 说明
0 FIT_CENTER 保持比例,居中显示,可能有黑边
1 FIT_START 保持比例,靠上显示
2 FIT_END 保持比例,靠下显示
3 FILL_CENTER 填充视图,居中裁剪,无黑边
4 FILL_START 填充视图,靠上裁剪
5 FILL_END 填充视图,靠下裁剪

Events 事件

事件名 说明 回调参数
onCameraReady 相机就绪 { detail: { message } }
onError 错误回调 { detail: { message } }
onPoseDetected 姿态检测 { detail: { timestamp, landmarks, worldLandmarks } }
onMotionRecognized 运动识别 { detail: { action, confidence, details } }
onGestureDetected 手势检测 { detail: { gestures } }
onFaceDetected 面部检测 { detail: { timestamp, landmarks, score } }
onKeyFrameCaptured 截图完成 { detail: { imagePath } }

Methods 方法

方法名 参数 说明
startCamera() - 启动相机
stopCamera() - 停止相机
switchCamera() - 切换摄像头
toggleFlash() - 切换闪光灯,返回当前状态
setTorchMode(mode) "on" / "off" 设置闪光灯模式
changeScaleType(type) 0-5 动态更改缩放模式
enablePoseDetection() - 启用姿态检测
disablePoseDetection() - 禁用姿态检测
enableGestureDetection() - 启用手势检测
disableGestureDetection() - 禁用手势检测
enableFaceDetection() - 启用面部检测
disableFaceDetection() - 禁用面部检测
resetMotionCounters() - 重置运动计数器
getMotionCounts() - 获取运动计数 Map
setShowLandmarks(show) boolean 设置是否显示关键点
captureFrame() - 立即截图,返回路径
requestKeyFrame() - 请求截图(异步回调)
prepareForUnload() - 准备卸载,释放资源

回调数据说明

onPoseDetected

{
  timestamp: number,        // 时间戳
  landmarks: [{            // 33 个关键点(归一化坐标)
    x: number, y: number, z: number,
    visibility: number, presence: number
  }],
  worldLandmarks: [{       // 3D 世界坐标
    x: number, y: number, z: number
  }]
}

onMotionRecognized

{
  action: string,          // 动作名称
  confidence: number,      // 置信度 0-1
  details: {               // 计数详情
    squatCount: number,
    pushUpCount: number,
    jumpingJackCount: number,
    // ... 更多动作计数
  }
}

onGestureDetected

{
  gestures: [{
    gesture: string,       // 手势名称
    confidence: number,    // 置信度
    isLeftHand: boolean,   // 是否左手
    score: number          // 手部检测分数
  }]
}

支持的手势

手势 名称
OK OK手势
POINTING 指向
PEACE 剪刀手
ROCK 摇滚
THUMBS_UP 竖大拇指
OPEN_PALM 张开手掌
FIST 握拳

支持的运动

运动 名称 计数键
深蹲 STR_SQUAT squatCount
俯卧撑 STR_PUSHUP pushUpCount
开合跳 STR_JUMPINGJACK jumpingJackCount
弓步 STR_LUNGE lungeCount
举手 STR_ARMRAISE armRaiseCount
高抬腿 STR_HIGHKNEE highKneeCount
侧平举 STR_SIDERAISE sideRaiseCount
卷腹 STR_CRUNCH crunchCount
臀桥 STR_GLUTEBRIDGE gluteBridgeCount
踢腿 STR_KICK kickCount
平板支撑 STR_PLANK -

注意事项

  1. 本插件仅支持 Android 端
  2. 最低支持 Android 6.0 (API 23)
  3. 建议使用 HBuilderX Alpha 版本进行开发
  4. 页面卸载时务必调用 prepareForUnload() 释放资源

隐私、权限声明

1. 本插件需要申请的系统权限列表:

Android: 相机、麦克风、存储权限

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集任何数据

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

暂无用户评论。