Files
MrleeAdmin/frontend/app/docs/mas-code-scan-识别区域方案.md
T

13 KiB
Raw Blame History

mas-code-scan 自定义识别区域方案

1. 目标

在 Android 扫码页面中提供可配置的实时识别区域(ROI,Region of Interest):

  • 摄像头打开后显示一个矩形扫描框;
  • 只有条码位于矩形区域内时才允许识别成功;
  • 扫描框外的条码不触发单次扫码成功;
  • 支持自定义扫描框宽度、高度、水平位置和垂直位置;
  • 支持不同屏幕尺寸和横竖屏适配;
  • 不破坏现有的单次扫码、连续扫码、相册识图、多码点选、智能变焦和码制过滤能力。

2. 当前实现核对

2.1 现有接口

文件:uni_modules/mas-code-scan/utssdk/interface.uts

当前 CodeScanOptions 支持:

{
  smartZoom?: boolean
  formats?: string[]
  vibrate?: boolean
  continuous?: boolean
  fromAlbum?: boolean
  success?: (res) => void
  fail?: (res) => void
  complete?: (res) => void
}

当前没有识别区域配置字段。

2.2 预览层

文件:uni_modules/mas-code-scan/utssdk/app-android/CodeCaptureActivity.kt

  • 使用 PreviewView.ScaleType.FILL_CENTER 显示相机预览;
  • ScanFrameDecorView 覆盖在预览层上方绘制扫描动画;
  • 预览层和装饰层都是全屏 FrameLayout;
  • 触摸缩放绑定在 PreviewView 上。

文件:uni_modules/mas-code-scan/utssdk/app-android/ScanFrameDecorView.kt

当前扫描框是固定的正方形:

val frameWidth = ...
val frameHeight = frameWidth

它只负责画扫描线,不参与实际识别。

2.3 识别层

文件:uni_modules/mas-code-scan/utssdk/app-android/CodeCaptureActivity.kt

当前把完整的 ImageProxy 图像交给 ML Kit:

val inputImage = InputImage.fromMediaImage(mediaImage, rotationDegrees)
scanner.process(inputImage)

因此当前扫描框只是视觉提示,框外条码仍可能被识别。

3. 推荐接口设计

建议新增 scanRegion,使用相对比例配置,不使用固定像素作为公共接口。

openCodeScan({
  scanRegion: {
    width: 0.92,
    height: 0.18,
    centerX: 0.5,
    centerY: 0.5
  }
})

字段定义:

字段 类型 默认值 范围 含义
fullScreen boolean false - 为 true 时整画面识别,不绘制扫描框且不进行区域过滤
width number 0.74 0.1 ~ 1 扫描框宽度,占预览宽度的比例
height number 0.74 0.1 ~ 1 扫描框高度,占预览宽度的比例
centerX number 0.5 0 ~ 1 扫描框中心的水平位置比例
centerY number 0.5 0 ~ 1 扫描框中心的垂直位置比例

全屏识别示例:

openCodeScan({
  scanRegion: { fullScreen: true }
})

二维码区域约束:

  • width 与 height 使用相同值,严格锁定 1:1 正方形比例;
  • 默认值为 width: 0.74、height: 0.74;
  • 原生扫描框使用直角,不绘制圆角。

推荐默认值:

{
  width: 0.74,
  height: 0.74,
  centerX: 0.5,
  centerY: 0.5
}

业务页面可针对一维码使用横向区域,并通过 centerY 调整上下位置、通过 height 调整框高:

{
  width: 0.96,
  height: 0.24,
  centerX: 0.5,
  centerY: 0.5
}

原型页提供三档横向条码框高度:

  • 中:0.24(24%);
  • 大:0.32(32%);
  • 特大:0.40(40%)。

3.1 为什么使用比例

  • 不依赖具体手机分辨率和 density;
  • 适配不同 Android 设备;
  • 与当前全屏预览布局一致;
  • 后续可在页面中通过滑块或预设配置调整;
  • 避免将前端 rpx 直接与原生像素混用。

4. 实现分层

4.1 UTS 接口层

修改:utssdk/interface.uts

新增:

export type ScanRegion = {
  width?: number
  height?: number
  centerX?: number
  centerY?: number
}

并在 CodeScanOptions 中增加:

scanRegion?: ScanRegion

要求:

  • 字段全部可选;
  • 不传时保持现有默认扫描框;
  • 对非法值进行原生端裁剪和兜底;
  • 不把 UI 颜色、文案等表现层参数与识别区域参数混在一起。

4.2 UTS 到 Android Intent

修改:utssdk/app-android/index.uts

新增一个 JSON 字符串 Extra,例如:

mas_scan_region

将 scanRegion 序列化后传递给 CodeCaptureActivity。

推荐传递 JSON,而不是增加多个 Intent Extra,原因是:

  • 字段集中;
  • 后续增加圆角、边框颜色等非识别参数时更容易扩展;
  • 保持接口和 Activity 参数数量可控。

4.3 Activity 参数解析

修改:CodeCaptureActivity.kt

新增:

private data class ScanRegionConfig(
    val width: Float,
    val height: Float,
    val centerX: Float,
    val centerY: Float
)

初始化时:

  1. 读取 Intent Extra;
  2. 解析 JSON;
  3. 将数值限制在安全范围;
  4. 对过小区域、越界中心点进行兜底;
  5. 将同一份配置传给 ScanFrameDecorView 和识别过滤逻辑。

最终必须保证:

视觉扫描框区域 == 实际有效识别区域

5. 坐标体系设计

这是本需求的核心难点。项目中至少存在三套坐标:

  1. 屏幕/装饰层坐标:ScanFrameDecorView 的 view 坐标;
  2. 预览可视区域坐标:PreviewView 中用户看到的相机画面;
  3. ImageProxy 图像坐标:ML Kit Barcode.boundingBox 使用的图像坐标。

由于预览使用 FILL_CENTER,图像可能在屏幕两侧或上下被裁剪,不能简单按照屏幕宽高直接换算。

5.1 统一区域定义

推荐先在预览层坐标中计算扫描框:

frameLeft   = viewWidth  * (centerX - width / 2)
frameTop    = viewHeight * (centerY - height / 2)
frameRight  = viewWidth  * (centerX + width / 2)
frameBottom = viewHeight * (centerY + height / 2)

并限制在预览层范围内。

5.2 预览坐标映射到图像坐标

当前 PreviewView 使用 FILL_CENTER,映射应考虑:

scale = max(viewWidth / rotatedImageWidth, viewHeight / rotatedImageHeight)

相机图像在屏幕上的实际显示尺寸为:

displayWidth  = rotatedImageWidth  * scale
displayHeight = rotatedImageHeight * scale
offsetX = (viewWidth  - displayWidth) / 2
offsetY = (viewHeight - displayHeight) / 2

扫描框转回旋转后的图像坐标:

imageLeft   = (frameLeft   - offsetX) / scale
imageTop    = (frameTop    - offsetY) / scale
imageRight  = (frameRight  - offsetX) / scale
imageBottom = (frameBottom - offsetY) / scale

然后再根据 rotationDegrees 转换到 ImageProxy 原始坐标。

注意:实际代码需要以 PreviewView 的 viewPort、绑定的 UseCase 分辨率和设备方向为准验证,不能只凭静态公式上线。

6. 识别限制策略

6.1 第一阶段:结果过滤,推荐先实现

ML Kit 仍分析整帧,但只接受扫描区域内的条码:

val validBarcodes = barcodes.filter { barcode ->
    val box = barcode.boundingBox ?: return@filter false
    isBarcodeInsideScanRegion(box, imageProxy, rotationDegrees)
}

区域判断建议使用条码边界框中心点,默认规则:

条码中心点位于扫描框内 => 允许识别
条码中心点位于扫描框外 => 忽略

对于严格业务场景,可以改为:

条码边界框完全位于扫描框内 => 允许识别

本项目建议默认使用“中心点在区域内”,原因是:

  • 用户将条码移入框内时体验更自然;
  • 条码边缘轻微超出时不会频繁丢失;
  • 对一维横向条码更友好。

6.2 多码识别

过滤应发生在多码判断之前:

原始 barcodes
  -> 过滤无 boundingBox 的结果
  -> 过滤框外结果
  -> 再执行单码 / 多码判断

这样框外多码不会触发点选页面,也不会影响智能变焦。

6.3 相册识图

实时摄像头的 scanRegion 不应默认限制相册识图:

  • 相册图片没有实时预览框坐标;
  • 用户选择的是整张图片;
  • 继续保持相册识别整图的现有行为。

如果未来确实需要对相册图片裁剪,应单独设计 albumRegion,不要复用实时预览坐标。

6.4 识别前裁剪

第二阶段可选做真正的 ROI 图像裁剪,只将区域图像提交给 ML Kit。

暂不作为第一阶段实现,原因:

  • 需要处理 ImageProxy YUV 裁剪;
  • 需要处理旋转和 cropRect;
  • 需要重新映射多码点选坐标;
  • 可能影响智能变焦依据和现有连续扫码稳定性;
  • 设备兼容性验证成本更高。

7. 扫描框视觉设计

修改:ScanFrameDecorView.kt

扫描框应从固定正方形改为配置矩形:

  • 宽度由 width 决定;
  • 高度由 height 决定;
  • 位置由 centerX、centerY 决定;
  • 扫描线只在矩形内部移动;
  • 扫描线左右留出固定 inset;
  • 其他区域可增加半透明遮罩,增强用户对有效范围的理解。

建议提供清晰的 UI 语义:

框内:有效识别区域
框外:不识别

当前截图的横向条码场景推荐:

scanRegion: {
  width: 0.96,
  height: 0.16,
  centerX: 0.5,
  centerY: 0.5
}

8. 原型页面示例规划

文件:pages/scan/prototype.vue

原型页增加“识别区域”配置区,但不直接实现摄像头 UI。页面负责展示和传递配置:

8.1 预设

提供三个预设:

  • 默认正方形:适合二维码;
  • 横向条码:适合设备码、物料码;
  • 自定义:通过宽度、高度、水平位置、垂直位置调整。

8.2 示例数据

scanRegion: {
  width: 0.96,
  height: 0.16,
  centerX: 0.5,
  centerY: 0.5
}

8.3 原型页交互

  • 使用滑块或步进按钮调整四个比例参数;
  • 页面实时显示当前区域参数;
  • 单次扫码、连续扫码都使用当前 scanRegion;
  • 相册识图按钮不传实时扫描区域,继续整图识别;
  • 在 Web 预览中展示“仅 Android App-Plus 支持原生扫码”的提示。

9. 兼容性策略

9.1 不传配置

现有调用全部保持兼容:

openCodeScan({
  smartZoom: true,
  vibrate: true
})

行为继续使用默认正方形区域。

9.2 旧构建包

如果前端代码传入 scanRegion,但运行的是旧插件包,旧原生代码会忽略未知 Extra,仍可正常扫码,但不会限制区域。因此必须重新制作自定义基座或正式 APK。

9.3 非 Android 平台

继续由 #ifdef APP-PLUS 控制插件导入和调用,Web、iOS、小程序不加载 Android UTS 实现。

9.4 参数安全

原生端必须再次校验参数,不能只依赖前端:

  • width、height 限制在 0.1 ~ 1;
  • centerX、centerY 限制在 0 ~ 1;
  • 区域超出屏幕时进行边界收敛;
  • 宽高过小导致不可用时回退默认值。

10. 实施顺序

阶段一:方案验证

  1. 增加 scanRegion 接口定义;
  2. 将参数传入 Android Activity;
  3. 扫描框支持矩形和位置配置;
  4. 根据条码中心点过滤框外结果;
  5. 原型页提供横向条码预设;
  6. 保持相册整图识别。

阶段二:真机验证

至少验证:

  • 二维码默认正方形识别;
  • 横向一维码识别;
  • 框内识别成功;
  • 框外完全不识别;
  • 条码横跨边界时的行为;
  • 多个条码一内一外时只处理框内条码;
  • 连续扫码只回传框内结果;
  • 智能变焦不会被框外条码触发;
  • 横竖屏和不同分辨率设备;
  • 相册识图不受实时区域影响;
  • 关闭、取消、权限拒绝和页面销毁流程。

阶段三:可选优化

根据真机效果再决定是否:

  • 改为识别前裁剪 ROI;
  • 增加边框颜色、圆角、遮罩透明度配置;
  • 增加“中心点在框内 / 完全包含”两种命中规则;
  • 增加一维码专用默认模板。

11. 验收标准

功能验收:

  • 扫描框宽高和位置可以通过 openCodeScan 参数调整;
  • 用户看到的扫描框与实际识别区域一致;
  • 框外条码不会触发成功回调;
  • 框内条码可以正常触发成功回调;
  • 多码点选只展示有效区域内的条码;
  • 旧调用不传 scanRegion 时行为不变;
  • 单次、连续、相册三种模式互不回归。

工程验收:

  • UTS 接口类型、Intent Extra、Activity 解析三层字段一致;
  • 不在页面中直接修改原生插件内部逻辑;
  • 所有新增配置有默认值和越界保护;
  • 真机重新构建自定义基座后验证;
  • 不以 Web 预览结果代替 Android 真机验证。

12. 结论

本项目适合采用“比例配置 + 原生扫描框绘制 + 条码结果区域过滤”作为第一版实现。

该方案能满足截图所示的横向矩形识别场景,改动范围可控,且能保留当前插件已有能力。真正的图像裁剪 ROI 作为后续优化,不在第一版中引入,以降低旋转、坐标映射和多码点选的回归风险。