13 KiB
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
)
初始化时:
- 读取 Intent Extra;
- 解析 JSON;
- 将数值限制在安全范围;
- 对过小区域、越界中心点进行兜底;
- 将同一份配置传给
ScanFrameDecorView和识别过滤逻辑。
最终必须保证:
视觉扫描框区域 == 实际有效识别区域
5. 坐标体系设计
这是本需求的核心难点。项目中至少存在三套坐标:
- 屏幕/装饰层坐标:
ScanFrameDecorView的 view 坐标; - 预览可视区域坐标:
PreviewView中用户看到的相机画面; - 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. 实施顺序
阶段一:方案验证
- 增加
scanRegion接口定义; - 将参数传入 Android Activity;
- 扫描框支持矩形和位置配置;
- 根据条码中心点过滤框外结果;
- 原型页提供横向条码预设;
- 保持相册整图识别。
阶段二:真机验证
至少验证:
- 二维码默认正方形识别;
- 横向一维码识别;
- 框内识别成功;
- 框外完全不识别;
- 条码横跨边界时的行为;
- 多个条码一内一外时只处理框内条码;
- 连续扫码只回传框内结果;
- 智能变焦不会被框外条码触发;
- 横竖屏和不同分辨率设备;
- 相册识图不受实时区域影响;
- 关闭、取消、权限拒绝和页面销毁流程。
阶段三:可选优化
根据真机效果再决定是否:
- 改为识别前裁剪 ROI;
- 增加边框颜色、圆角、遮罩透明度配置;
- 增加“中心点在框内 / 完全包含”两种命中规则;
- 增加一维码专用默认模板。
11. 验收标准
功能验收:
- 扫描框宽高和位置可以通过
openCodeScan参数调整; - 用户看到的扫描框与实际识别区域一致;
- 框外条码不会触发成功回调;
- 框内条码可以正常触发成功回调;
- 多码点选只展示有效区域内的条码;
- 旧调用不传
scanRegion时行为不变; - 单次、连续、相册三种模式互不回归。
工程验收:
- UTS 接口类型、Intent Extra、Activity 解析三层字段一致;
- 不在页面中直接修改原生插件内部逻辑;
- 所有新增配置有默认值和越界保护;
- 真机重新构建自定义基座后验证;
- 不以 Web 预览结果代替 Android 真机验证。
12. 结论
本项目适合采用“比例配置 + 原生扫描框绘制 + 条码结果区域过滤”作为第一版实现。
该方案能满足截图所示的横向矩形识别场景,改动范围可控,且能保留当前插件已有能力。真正的图像裁剪 ROI 作为后续优化,不在第一版中引入,以降低旋转、坐标映射和多码点选的回归风险。