截图自动分类封面

前面几篇把 OCR、超分、文搜图、人脸检测都单独讲了一遍。这篇开始把它们串起来,做一个实际的功能:截图自动分类。

我们的智能截图整理工具的核心需求就是:用户截了一堆图,App 自动帮你分类——哪些是聊天记录、哪些是文档、哪些是截图、哪些是人脸照片。分好类之后用户找起来方便,也不用自己手动整理。

这个功能的难点不在于调单个 API,而在于怎么把多个视觉能力组合起来,做一套分类规则,并且分类结果要稳定、准确。

一、真实开发中遇到的问题

最开始想的是:一张截图进来,OCR 提取文字,然后根据文字内容分类。简单粗暴。

实际试了一下发现不行:

第一,纯文字分类不够准。一张微信聊天截图和一张文档截图,文字都是中文,怎么区分?光靠文字内容分不出来。

第二,有些截图文字很少。比如一张纯风景截图,OCR 提取不出几个字,怎么分类?

第三,分类标准不清晰。什么叫"聊天记录"?什么叫"文档"?边界在哪里?

第四,用户量一大,处理速度跟不上。用户一次导入 50 张截图,每张都要 OCR + 人脸检测 + 关键词分析,串行处理要等好久。

二、这个能力怎么接入

我们最后用了多特征组合的分类思路,一张截图同时跑三个检测:

1. OCR 文字提取:提取截图里的所有文字。

2. 人脸检测:判断截图里有没有人脸。

3. 图像分类:用多模态模型判断这张图是什么类型(照片/截图/文档)。

然后根据三个结果组合出最终分类:

有文字 + 没人脸 + 文字密度高 → 文档类

有文字 + 有聊天界面特征(时间戳、头像、气泡)→ 聊天记录类

有人脸 → 人像类

没文字 + 色彩丰富 → 图片类

这样比单靠 OCR 分类准多了。

分类决策流程图

三、关键代码怎么写

下面是封装的截图分类器,文件位置在 entry/src/main/ets/utils/ScreenshotClassifier.ets

import { image } from '@kit.CoreImageKit';
import { OcrUtil } from './OcrUtil';
import { FaceDetectUtil } from './FaceDetectUtil';

export class ScreenshotClassifier {
  
  // 分类主入口
  static async classify(
    pixelMap: image.PixelMap
  ): Promise<ScreenshotCategory> {
    
    // 1. OCR 提取文字
    const ocrResult = await OcrUtil.extractTextFromImage(pixelMap);
    const text = ocrResult.text;
    
    // 2. 人脸检测
    const hasFace = await FaceDetectUtil.hasFace(pixelMap);
    
    // 3. 关键词分析
    const keywords = this.extractKeywords(text);
    
    // 4. 综合判断分类
    return this.decideCategory(text, hasFace, keywords);
  }

  // 提取关键词
  private static extractKeywords(text: string): string[] {
    if (!text) return [];
    
    // 简单的关键词匹配
    const keywordMap: Record<string, string> = {
      '聊天记录': ['聊天', '微信', '消息', '发送'],
      '文档': ['合同', '协议', '报告', '文档', 'PDF'],
      '票据': ['发票', '报销', '金额', '元'],
      '截图': ['截图', '屏幕', '系统', '设置']
    };
    
    const found: string[] = [];
    for (const [category, words] of Object.entries(keywordMap)) {
      for (const word of words) {
        if (text.includes(word)) {
          found.push(category);
          break;
        }
      }
    }
    return found;
  }

  // 综合决策分类
  private static decideCategory(
    text: string,
    hasFace: boolean,
    keywords: string[]
  ): ScreenshotCategory {
    
    // 优先按关键词分类
    if (keywords.includes('票据')) return {
      type: 'invoice',
      label: '票据',
      confidence: 0.85
    };
    
    if (keywords.includes('聊天记录')) return {
      type: 'chat',
      label: '聊天记录',
      confidence: 0.8
    };
    
    // 有人脸 → 人像类
    if (hasFace) return {
      type: 'portrait',
      label: '人像',
      confidence: 0.75
    };
    
    // 文字多 → 文档类
    if (text.length > 100) return {
      type: 'document',
      label: '文档',
      confidence: 0.7
    };
    
    // 兜底:图片类
    return {
      type: 'image',
      label: '图片',
      confidence: 0.5
    };
  }
}

// 分类结果类型
interface ScreenshotCategory {
  type: string;
  label: string;
  confidence: number;
}

这段代码的核心:classify 方法同时跑 OCR 和人脸检测,然后 extractKeywords 做关键词匹配,decideCategory 综合所有特征做最终分类。

批量处理的时候怎么提高速度?文件位置 pages/ScreenshotListPage.ets

@Entry
@Component
struct ScreenshotListPage {
  @State screenshots: ScreenshotItem[] = [];
  @State isProcessing: boolean = false;
  @State progress: number = 0;

  // 批量导入截图并分类
  async onImportScreenshots(images: image.PixelMap[]) {
    this.isProcessing = true;
    this.progress = 0;
    const total = images.length;
    
    const results: ScreenshotItem[] = [];
    
    // 串行处理,避免内存压力
    for (let i = 0; i < images.length; i++) {
      const pixelMap = images[i];
      
      // 执行分类
      const category = await ScreenshotClassifier.classify(pixelMap);
      
      results.push({
        image: pixelMap,
        category: category
      });
      
      this.progress = Math.round(((i + 1) / total) * 100);
      
      // 释放内存
      pixelMap.release();
    }
    
    this.screenshots = results;
    this.isProcessing = false;
  }

  build() {
    Column() {
      if (this.isProcessing) {
        // 进度条
        Column() {
          Text('正在分类... ' + this.progress + '%')
          Progress({ value: this.progress, total: 100 })
        }
      } else {
        // 按分类分组展示
        List() {
          ForEach(this.groupByCategory(), (group: CategoryGroup) => {
            ListItem() {
              Column() {
                Text(group.label + ' (' + group.items.length + ')')
                // 该分类下的截图列表
                Grid() {
                  ForEach(group.items, (item: ScreenshotItem) => {
                    GridItem() {
                      Image(item.image)
                        .width(100).height(100)
                    }
                  })
                }
                .columnsTemplate('1fr 1fr 1fr')
              }
            }
          })
        }
      }
    }
  }
}

分类结果界面效果

四、运行过程中怎么处理异常

截图分类的异常场景:

1. OCR 失败:某张图识别失败了,text 为空。没关系,继续做人脸检测和图像分类,多特征组合就是为了兜底。

2. 分类置信度低:如果所有特征都不明显,confidence 低于 0.5,就归到"其他"类,不要硬分到某个类别里。

3. 批量处理中断:处理到一半用户退出去了,要保存已经处理的结果,下次进来继续,不要从头再来。

4. 内存压力:批量处理的时候,每张图的 PixelMap 用完就释放,不要一直持着。

五、实际开发中容易忽略的问题

1. 分类规则要可配置:关键词和分类规则不要写死在代码里,做成可配置的,后面调整规则不用改代码。

2. 分类结果要展示置信度:用户看到"文档类 70%",比只看到"文档类"更可信。置信度低的分类,UI 上可以标注一下。

3. 批量处理要限流:一次不要同时跑太多张,串行处理最稳。如果想并行,最多 2-3 个,再多就 OOM 了。

4. 分类结果要缓存:同一张截图不要每次都重新分类。分类结果存到本地数据库,下次直接读。

5. 用户可以手动修正:AI 分类总有不准的时候。UI 上要允许用户手动调整分类,并且把修正结果记下来,后面分类可以越来越准。

截图自动分类是前几篇能力的第一个综合应用。OCR、人脸检测、关键词分析,每个单独拿出来都不难,但组合起来做成一个稳定好用的功能,就要在工程细节上下功夫。下一篇会把整个处理链串起来,做一个完整的流水线。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐