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

499 lines
13 KiB
Markdown
Raw Normal View History

2026-08-31 11:33:59 +08:00
# 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 作为后续优化,不在第一版中引入,以降低旋转、坐标映射和多码点选的回归风险。