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

499 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# mas-code-scan 自定义识别区域方案
## 1. 目标
在 Android 扫码页面中提供可配置的实时识别区域(ROI,Region of Interest):
- 摄像头打开后显示一个矩形扫描框;
- 只有条码位于矩形区域内时才允许识别成功;
- 扫描框外的条码不触发单次扫码成功;
- 支持自定义扫描框宽度、高度、水平位置和垂直位置;
- 支持不同屏幕尺寸和横竖屏适配;
- 不破坏现有的单次扫码、连续扫码、相册识图、多码点选、智能变焦和码制过滤能力。
## 2. 当前实现核对
### 2.1 现有接口
文件:`uni_modules/mas-code-scan/utssdk/interface.uts`
当前 `CodeScanOptions` 支持:
```ts
{
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`
当前扫描框是固定的正方形:
```kotlin
val frameWidth = ...
val frameHeight = frameWidth
```
它只负责画扫描线,不参与实际识别。
### 2.3 识别层
文件:`uni_modules/mas-code-scan/utssdk/app-android/CodeCaptureActivity.kt`
当前把完整的 `ImageProxy` 图像交给 ML Kit:
```kotlin
val inputImage = InputImage.fromMediaImage(mediaImage, rotationDegrees)
scanner.process(inputImage)
```
因此当前扫描框只是视觉提示,框外条码仍可能被识别。
## 3. 推荐接口设计
建议新增 `scanRegion`,使用相对比例配置,不使用固定像素作为公共接口。
```ts
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` | 扫描框中心的垂直位置比例 |
全屏识别示例:
```ts
openCodeScan({
scanRegion: { fullScreen: true }
})
```
二维码区域约束:
- `width` 与 `height` 使用相同值,严格锁定 `1:1` 正方形比例;
- 默认值为 `width: 0.74`、`height: 0.74`;
- 原生扫描框使用直角,不绘制圆角。
推荐默认值:
```ts
{
width: 0.74,
height: 0.74,
centerX: 0.5,
centerY: 0.5
}
```
业务页面可针对一维码使用横向区域,并通过 `centerY` 调整上下位置、通过 `height` 调整框高:
```ts
{
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`
新增:
```ts
export type ScanRegion = {
width?: number
height?: number
centerX?: number
centerY?: number
}
```
并在 `CodeScanOptions` 中增加:
```ts
scanRegion?: ScanRegion
```
要求:
- 字段全部可选;
- 不传时保持现有默认扫描框;
- 对非法值进行原生端裁剪和兜底;
- 不把 UI 颜色、文案等表现层参数与识别区域参数混在一起。
### 4.2 UTS 到 Android Intent
修改:`utssdk/app-android/index.uts`
新增一个 JSON 字符串 Extra,例如:
```text
mas_scan_region
```
将 `scanRegion` 序列化后传递给 `CodeCaptureActivity`。
推荐传递 JSON,而不是增加多个 Intent Extra,原因是:
- 字段集中;
- 后续增加圆角、边框颜色等非识别参数时更容易扩展;
- 保持接口和 Activity 参数数量可控。
### 4.3 Activity 参数解析
修改:`CodeCaptureActivity.kt`
新增:
```kotlin
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` 和识别过滤逻辑。
最终必须保证:
```text
视觉扫描框区域 == 实际有效识别区域
```
## 5. 坐标体系设计
这是本需求的核心难点。项目中至少存在三套坐标:
1. **屏幕/装饰层坐标**:`ScanFrameDecorView` 的 view 坐标;
2. **预览可视区域坐标**:`PreviewView` 中用户看到的相机画面;
3. **ImageProxy 图像坐标**:ML Kit `Barcode.boundingBox` 使用的图像坐标。
由于预览使用 `FILL_CENTER`,图像可能在屏幕两侧或上下被裁剪,不能简单按照屏幕宽高直接换算。
### 5.1 统一区域定义
推荐先在预览层坐标中计算扫描框:
```text
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`,映射应考虑:
```text
scale = max(viewWidth / rotatedImageWidth, viewHeight / rotatedImageHeight)
```
相机图像在屏幕上的实际显示尺寸为:
```text
displayWidth = rotatedImageWidth * scale
displayHeight = rotatedImageHeight * scale
offsetX = (viewWidth - displayWidth) / 2
offsetY = (viewHeight - displayHeight) / 2
```
扫描框转回旋转后的图像坐标:
```text
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 仍分析整帧,但只接受扫描区域内的条码:
```kotlin
val validBarcodes = barcodes.filter { barcode ->
val box = barcode.boundingBox ?: return@filter false
isBarcodeInsideScanRegion(box, imageProxy, rotationDegrees)
}
```
区域判断建议使用条码边界框中心点,默认规则:
```text
条码中心点位于扫描框内 => 允许识别
条码中心点位于扫描框外 => 忽略
```
对于严格业务场景,可以改为:
```text
条码边界框完全位于扫描框内 => 允许识别
```
本项目建议默认使用“中心点在区域内”,原因是:
- 用户将条码移入框内时体验更自然;
- 条码边缘轻微超出时不会频繁丢失;
- 对一维横向条码更友好。
### 6.2 多码识别
过滤应发生在多码判断之前:
```text
原始 barcodes
-> 过滤无 boundingBox 的结果
-> 过滤框外结果
-> 再执行单码 / 多码判断
```
这样框外多码不会触发点选页面,也不会影响智能变焦。
### 6.3 相册识图
实时摄像头的 `scanRegion` 不应默认限制相册识图:
- 相册图片没有实时预览框坐标;
- 用户选择的是整张图片;
- 继续保持相册识别整图的现有行为。
如果未来确实需要对相册图片裁剪,应单独设计 `albumRegion`,不要复用实时预览坐标。
### 6.4 识别前裁剪
第二阶段可选做真正的 ROI 图像裁剪,只将区域图像提交给 ML Kit。
暂不作为第一阶段实现,原因:
- 需要处理 ImageProxy YUV 裁剪;
- 需要处理旋转和 cropRect;
- 需要重新映射多码点选坐标;
- 可能影响智能变焦依据和现有连续扫码稳定性;
- 设备兼容性验证成本更高。
## 7. 扫描框视觉设计
修改:`ScanFrameDecorView.kt`
扫描框应从固定正方形改为配置矩形:
- 宽度由 `width` 决定;
- 高度由 `height` 决定;
- 位置由 `centerX`、`centerY` 决定;
- 扫描线只在矩形内部移动;
- 扫描线左右留出固定 inset;
- 其他区域可增加半透明遮罩,增强用户对有效范围的理解。
建议提供清晰的 UI 语义:
```text
框内:有效识别区域
框外:不识别
```
当前截图的横向条码场景推荐:
```ts
scanRegion: {
width: 0.96,
height: 0.16,
centerX: 0.5,
centerY: 0.5
}
```
## 8. 原型页面示例规划
文件:`pages/scan/prototype.vue`
原型页增加“识别区域”配置区,但不直接实现摄像头 UI。页面负责展示和传递配置:
### 8.1 预设
提供三个预设:
- 默认正方形:适合二维码;
- 横向条码:适合设备码、物料码;
- 自定义:通过宽度、高度、水平位置、垂直位置调整。
### 8.2 示例数据
```js
scanRegion: {
width: 0.96,
height: 0.16,
centerX: 0.5,
centerY: 0.5
}
```
### 8.3 原型页交互
- 使用滑块或步进按钮调整四个比例参数;
- 页面实时显示当前区域参数;
- 单次扫码、连续扫码都使用当前 `scanRegion`;
- 相册识图按钮不传实时扫描区域,继续整图识别;
- 在 Web 预览中展示“仅 Android App-Plus 支持原生扫码”的提示。
## 9. 兼容性策略
### 9.1 不传配置
现有调用全部保持兼容:
```ts
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 作为后续优化,不在第一版中引入,以降低旋转、坐标映射和多码点选的回归风险。