在这里插入图片描述

📖 引言

想象一下这个场景:你在「民族图鉴」App里浏览藏族服饰,看到一件精美的藏袍,想把自己的头像和这件藏袍合成一张照片,发到朋友圈。以前你得打开电脑,用Photoshop一点一点地抠图,花上十几分钟。现在,你在手机上点两下——AI自动识别藏袍的轮廓,精准抠出服饰元素,再和你的人像合成,几秒钟就完成。

这就是端侧AI图像编辑的魔力。

过去,图像编辑是专业软件的特权——抠图要PS,去除水印要PS,照片降噪要PS。普通用户没有这些技能,只能看着照片里的杂物、噪点、不想要的背景,无奈地接受。

但现在,有了鸿蒙7(API 26 Beta)带来的 @kit.CoreVisionKit,一切都变了:

  • 智能抠图:端侧AI精准识别主体,一键分离人物、物体、服饰
  • 物体消除:端侧AI智能填充,移除照片中的水印、路人、杂物
  • 图片降噪:AI降噪与增强,让模糊的老照片、暗光照片焕然一新
  • 人像抠图:精准分离人物与背景,边缘发丝级精度

更重要的是,这些能力全部在端侧运行,不需要联网,不需要上传到云端。你的照片始终在你的手机上,隐私安全有保障。

本文将以「民族图鉴」项目为场景,从Core Vision Kit的核心能力讲起,深入到智能抠图、物体消除、图片降噪的完整实现,包括API调用、图像处理管线、结果缓存、性能优化等关键技术,带你掌握端侧AI图像编辑的实战技能。


🎯 学习目标

完成本文后,你将能够:

  • ✅ 理解端侧AI图像编辑的核心原理与优势
  • ✅ 掌握 @kit.CoreVisionKit 的智能抠图API使用方法
  • ✅ 掌握物体消除与图片降噪的API调用流程
  • ✅ 理解人像抠图与通用抠图的区别与适用场景
  • ✅ 学会构建完整的图像处理管线(输入 → 处理 → 缓存 → 输出)
  • ✅ 掌握图像处理结果缓存策略,提升用户体验
  • ✅ 实现「民族图鉴」的民族服饰抠图、水印清除、老照片增强功能
  • ✅ 解决抠图边缘毛刺、消除效果差、降噪模糊等常见问题

💡 需求分析

什么是Core Vision Kit?

Core Vision Kit(@kit.CoreVisionKit)是鸿蒙7(API 26 Beta)新增的端侧AI视觉能力套件,提供了三大核心图像处理能力:

能力 说明 技术原理 典型场景
智能抠图 AI识别图像主体,分离前景与背景 基于深度学习的语义分割 + 边缘精修(Matting) 人像抠图、商品抠图、服饰抠图
物体消除 移除图中指定物体,AI自动填充背景 基于扩散模型的图像修复(Inpainting) 去水印、去路人、去杂物
图片降噪 AI降噪与画质增强 基于深度学习的图像去噪 + 超分辨率 老照片修复、暗光增强、模糊修复

这三项能力全部运行在端侧NPU(神经网络处理器)上,无需联网,响应速度快,隐私安全。

为什么选择端侧AI?

传统做法是:把图片上传到云端 → 云端AI处理 → 下载结果。这种方式有几个问题:

1. 隐私风险

  • 照片上传到云端,存在泄露风险
  • 民族图鉴中可能涉及博物馆未公开的文物照片
  • 用户个人照片更不能随便上传

2. 网络依赖

  • 需要网络连接,没网就用不了
  • 大图上传慢,体验差
  • 流量消耗大

3. 延迟高

  • 上传 + 排队 + 处理 + 下载,来回好几秒
  • 用户等待时间长,体验差

4. 成本高

  • 云端GPU服务器成本高
  • 用户越多,成本越高

端侧AI完美解决了这些问题:

  • 照片不出设备,隐私安全
  • 离线可用,随时随地
  • 本地NPU处理,毫秒级响应
  • 零服务器成本

「民族图鉴」的AI图像编辑应用场景

结合「民族图鉴」项目的特点,Core Vision Kit可以在以下场景发挥价值:

场景1:民族服饰抠图——制作民族特色头像

用户浏览民族服饰时,可以:

  • 一键抠出服饰中的特色元素(如苗族银饰、藏族腰带、蒙古族帽子)
  • 将自己的人像与抠出的服饰元素合成
  • 生成民族特色头像,分享到社交媒体

这是最核心的应用场景——让用户"穿上"各民族服饰,增强互动性和传播性。

场景2:博物馆照片去水印/去杂物

用户在博物馆拍摄民族文物照片时,经常遇到:

  • 照片中有反光、玻璃倒影
  • 背景有游客、杂物
  • 图片上有水印或文字

用物体消除功能,可以一键清除这些不需要的元素,得到干净的文物照片。

场景3:民族历史老照片增强

很多民族历史照片年代久远,存在:

  • 画面模糊、噪点多
  • 色彩暗淡、褪色
  • 细节不清

用图片降噪功能,可以增强老照片的清晰度,还原民族历史记忆。

场景4:民族特色贴纸制作
  • 抠出民族图案、纹样,做成透明背景的贴纸
  • 用户可以粘贴到自己的照片上
  • 制作民族主题的表情包

这些场景的核心价值在于:降低用户参与门槛。没有PS技能,也能轻松编辑民族主题图片,让更多人参与到民族文化的传播中来。

技术架构总览

在「民族图鉴」中,AI图像编辑功能的整体架构如下:

用户操作(选择图片 + 选择功能)
        ↓
图像编辑器(ImageEditor)
        ↓
    ┌───┴───┬───────┬───────┐
    ↓       ↓       ↓       ↓
智能抠图  物体消除  图片降噪  人像抠图
    ↓       ↓       ↓       ↓
    └───┬───┴───────┴───┬───┘
        ↓               ↓
Core Vision Kit API  (@kit.CoreVisionKit)
        ↓
端侧NPU推理引擎
        ↓
结果输出(像素图/透明背景图)
        ↓
    缓存管理(CacheManager)
        ↓
    结果展示 + 用户保存/分享

图像处理管线设计:

输入图片 → 预处理(缩放/格式转换) → AI处理 → 后处理(边缘平滑/颜色校正) → 缓存 → 输出

🛠️ 核心实现

步骤1:智能抠图——端侧AI分离前景与背景

1.1 智能抠图API概览

Core Vision Kit提供了两种抠图模式:

模式 说明 适用场景
人像抠图(Portrait Matting) 专门针对人物优化,发丝级精度 人物照片、自拍、合影
通用抠图(General Segmentation) 识别画面中的主体(物体、动物、建筑等) 商品、服饰、文物、建筑

两种模式都支持生成透明背景的PNG图片,方便后续合成。

1.2 人像抠图实现

首先看人像抠图——这是最常用的功能,精度要求最高。

/*
 * 文件用途:智能抠图工具类 - 人像抠图与通用抠图
 * 创建时间:2026-07-23
 * 兼容环境:HarmonyOS 7 API 26 / 端侧NPU
 * 版本:v1.0
 * 风险提示:首次加载模型需要约1-3秒初始化时间
 */

import { image } from '@kit.ImageKit';
import { visionCore } from '@kit.CoreVisionKit';
import { fileIo } from '@kit.CoreFileKit';

/**
 * 抠图结果
 */
interface MattingResult {
    /** 抠图后的像素数据(透明背景) */
    pixelMap: image.PixelMap;
    /** 抠图耗时(毫秒) */
    costTime: number;
    /** 是否成功 */
    success: boolean;
    /** 错误信息 */
    errorMsg?: string;
}

/**
 * 抠图配置选项
 */
interface MattingOptions {
    /** 抠图模式 */
    mode: 'portrait' | 'general';
    /** 是否启用边缘精修(发丝级精度,仅人像模式) */
    enableFineEdge?: boolean;
    /** 输出图片最大边长(用于性能优化,默认1024) */
    maxOutputSize?: number;
    /** 是否保存临时文件用于缓存 */
    enableCache?: boolean;
}

/**
 * 智能抠图管理器
 * 封装Core Vision Kit的抠图能力,提供统一的抠图接口
 */
class ImageMattingManager {
    private static instance: ImageMattingManager | null = null;
    private isModelReady: boolean = false;
    private modelInitPromise: Promise<void> | null = null;

    /**
     * 获取单例实例
     */
    static getInstance(): ImageMattingManager {
        if (!ImageMattingManager.instance) {
            ImageMattingManager.instance = new ImageMattingManager();
        }
        return ImageMattingManager.instance;
    }

    /**
     * 预加载AI模型(建议在应用启动时调用)
     * 首次抠图前必须调用,否则首次抠图会有1-3秒的模型加载延迟
     */
    async preloadModel(): Promise<void> {
        if (this.isModelReady) {
            return;
        }

        if (this.modelInitPromise) {
            // 模型正在加载中,等待完成
            return this.modelInitPromise;
        }

        this.modelInitPromise = this.initModel();
        await this.modelInitPromise;
        this.isModelReady = true;
        this.modelInitPromise = null;
    }

    /**
     * 初始化AI模型
     */
    private async initModel(): Promise<void> {
        try {
            console.info('[ImageMatting] 开始初始化AI抠图模型...');
            const startTime = Date.now();

            // 初始化Core Vision Kit的抠图引擎
            await visionCore.segmentation.init({
                modelType: 'portrait_matting',
                enableNpu: true,  // 启用NPU加速
                precision: 'float16'  // 半精度推理,平衡速度与精度
            });

            const costTime = Date.now() - startTime;
            console.info(`[ImageMatting] 模型初始化完成,耗时: ${costTime}ms`);
        } catch (error) {
            console.error('[ImageMatting] 模型初始化失败:', JSON.stringify(error));
            throw new Error('AI抠图模型加载失败,请稍后重试');
        }
    }

    /**
     * 执行人像抠图
     * @param sourcePath - 原始图片路径
     * @param options - 抠图选项
     * @returns 抠图结果
     */
    async portraitMatting(
        sourcePath: string,
        options?: MattingOptions
    ): Promise<MattingResult> {
        const startTime = Date.now();

        try {
            // 确保模型已加载
            await this.preloadModel();

            // 步骤1:读取原始图片
            const sourceImage = await this.loadImage(sourcePath);
            const sourceInfo = await sourceImage.getImageInfo();

            console.info(
                `[ImageMatting] 原始图片尺寸: ${sourceInfo.size.width}x${sourceInfo.size.height}`
            );

            // 步骤2:图片预处理(缩放至合理尺寸,提升处理速度)
            const maxSize = options?.maxOutputSize || 1024;
            const processedImage = await this.preprocessImage(sourceImage, maxSize);

            // 步骤3:执行AI抠图
            const enableFineEdge = options?.enableFineEdge !== false;
            console.info(`[ImageMatting] 开始人像抠图,精细边缘: ${enableFineEdge}`);

            const segmentationResult = await visionCore.segmentation.segment({
                pixelMap: processedImage,
                mode: 'portrait',
                enableFineEdge: enableFineEdge,
                returnMask: false,  // 直接返回抠图结果,不需要掩码
                outputFormat: 'rgba'  // 输出RGBA格式,Alpha通道为透明信息
            });

            // 步骤4:释放中间图片资源
            await sourceImage.release();
            await processedImage.release();

            const costTime = Date.now() - startTime;
            console.info(`[ImageMatting] 人像抠图完成,耗时: ${costTime}ms`);

            return {
                pixelMap: segmentationResult.pixelMap,
                costTime: costTime,
                success: true
            };
        } catch (error) {
            const costTime = Date.now() - startTime;
            console.error('[ImageMatting] 人像抠图失败:', JSON.stringify(error));

            return {
                pixelMap: null as unknown as image.PixelMap,
                costTime: costTime,
                success: false,
                errorMsg: '抠图失败,请确认图片中包含清晰的人像'
            };
        }
    }

    /**
     * 执行通用抠图(用于物体、服饰、文物等)
     * @param sourcePath - 原始图片路径
     * @param subjectHint - 主体提示(可选,帮助AI识别目标)
     * @returns 抠图结果
     */
    async generalMatting(
        sourcePath: string,
        subjectHint?: string
    ): Promise<MattingResult> {
        const startTime = Date.now();

        try {
            await this.preloadModel();

            const sourceImage = await this.loadImage(sourcePath);
            const processedImage = await this.preprocessImage(sourceImage, 1024);

            console.info(`[ImageMatting] 开始通用抠图,主体提示: ${subjectHint || '自动识别'}`);

            const segmentationResult = await visionCore.segmentation.segment({
                pixelMap: processedImage,
                mode: 'general',
                subjectHint: subjectHint, // 如 'clothing' 'accessory' 'artifact'
                returnMask: false,
                outputFormat: 'rgba'
            });

            await sourceImage.release();
            await processedImage.release();

            const costTime = Date.now() - startTime;
            console.info(`[ImageMatting] 通用抠图完成,耗时: ${costTime}ms`);

            return {
                pixelMap: segmentationResult.pixelMap,
                costTime: costTime,
                success: true
            };
        } catch (error) {
            const costTime = Date.now() - startTime;
            console.error('[ImageMatting] 通用抠图失败:', JSON.stringify(error));

            return {
                pixelMap: null as unknown as image.PixelMap,
                costTime: costTime,
                success: false,
                errorMsg: '抠图失败,请尝试选择主体更清晰的图片'
            };
        }
    }

    /**
     * 加载图片文件为PixelMap
     */
    private async loadImage(filePath: string): Promise<image.PixelMap> {
        try {
            const file = fileIo.openSync(filePath, fileIo.OpenMode.READ_ONLY);
            const imageSource = image.createImageSource(file.fd);
            const pixelMap = await imageSource.createPixelMap();
            await imageSource.release();
            fileIo.closeSync(file);
            return pixelMap;
        } catch (error) {
            console.error('[ImageMatting] 图片加载失败:', JSON.stringify(error));
            throw new Error('图片文件读取失败,请检查文件路径');
        }
    }

    /**
     * 图片预处理:缩放到合理尺寸
     * 大图直接处理会很慢,先缩放到合适尺寸再处理
     */
    private async preprocessImage(
        source: image.PixelMap,
        maxSize: number
    ): Promise<image.PixelMap> {
        const imageInfo = await source.getImageInfo();
        const width = imageInfo.size.width;
        const height = imageInfo.size.height;

        // 如果图片已经足够小,直接返回
        if (width <= maxSize && height <= maxSize) {
            return source;
        }

        // 计算缩放比例(保持宽高比)
        const scale = Math.min(maxSize / width, maxSize / height);
        const newWidth = Math.round(width * scale);
        const newHeight = Math.round(height * scale);

        console.info(
            `[ImageMatting] 图片预处理缩放: ${width}x${height}${newWidth}x${newHeight}`
        );

        // 创建缩放后的PixelMap
        const scaledPixelMap = await source.scale(newWidth, newHeight);
        return scaledPixelMap;
    }

    /**
     * 释放资源
     */
    release(): void {
        this.isModelReady = false;
        this.modelInitPromise = null;
    }
}

export { ImageMattingManager, MattingResult, MattingOptions };

关键设计点

  1. 单例模式:全局只有一个抠图管理器,避免重复加载模型
  2. 模型预加载:应用启动时预加载模型,首次抠图无延迟
  3. 图片预处理:大图先缩放到1024px以内再处理,速度提升显著
  4. 精细边缘模式:人像抠图开启发丝级精度,通用抠图可以关闭以提速
  5. 资源释放:处理完及时释放中间PixelMap,避免内存泄漏
1.3 通用抠图——民族服饰元素提取

对于「民族图鉴」的核心场景——提取民族服饰中的特色元素,通用抠图更加合适。因为服饰元素(银饰、腰带、帽子等)不是人像,人像抠图模式无法识别。

/*
 * 文件用途:民族元素抠图服务 - 提取服饰中的特色元素并生成透明背景图
 * 创建时间:2026-07-23
 * 兼容环境:HarmonyOS 7 API 26 / 端侧NPU
 * 版本:v1.0
 */

import { image } from '@kit.ImageKit';
import { ImageMattingManager } from './ImageMattingManager';

/**
 * 民族元素类型
 */
type EthnicElementType = 'headdress' | 'accessory' | 'clothing' | 'pattern' | 'embroidery';

/**
 * 民族元素抠图请求
 */
interface EthnicElementExtractRequest {
    /** 图片路径 */
    imagePath: string;
    /** 民族分类(如 'miao', 'tibetan', 'mongolian') */
    ethnicGroup: string;
    /** 元素类型 */
    elementType: EthnicElementType;
    /** 是否生成缩略图用于预览 */
    generateThumbnail?: boolean;
}

/**
 * 民族元素抠图结果
 */
interface EthnicElementExtractResult {
    /** 抠图后的像素数据 */
    pixelMap: image.PixelMap;
    /** 缩略图(如果有) */
    thumbnail?: image.PixelMap;
    /** 元素类型 */
    elementType: EthnicElementType;
    /** 处理耗时(毫秒) */
    costTime: number;
    /** 是否成功 */
    success: boolean;
    /** 错误信息 */
    errorMsg?: string;
}

/**
 * 主体提示映射表
 * 根据元素类型,给AI提供合适的主体提示词,提高抠图准确率
 */
const SUBJECT_HINT_MAP: Record<EthnicElementType, string> = {
    headdress: 'headwear',
    accessory: 'jewelry accessory',
    clothing: 'clothing garment',
    pattern: 'pattern decoration',
    embroidery: 'embroidery textile'
};

/**
 * 民族元素抠图服务
 * 专门用于从民族服饰图片中提取特色元素
 */
class EthnicElementExtractor {
    private mattingManager: ImageMattingManager;

    constructor() {
        this.mattingManager = ImageMattingManager.getInstance();
    }

    /**
     * 提取民族元素
     * @param request - 抠图请求
     * @returns 抠图结果
     */
    async extractElement(
        request: EthnicElementExtractRequest
    ): Promise<EthnicElementExtractResult> {
        const startTime = Date.now();

        try {
            console.info(
                `[EthnicExtractor] 开始提取民族元素: ${request.ethnicGroup}/${request.elementType}`
            );

            // 获取主体提示词
            const subjectHint = SUBJECT_HINT_MAP[request.elementType];
            const ethnicHint = `${request.ethnicGroup} ${subjectHint}`;

            console.info(`[EthnicExtractor] 主体提示: ${ethnicHint}`);

            // 调用通用抠图(带主体提示)
            const result = await this.mattingManager.generalMatting(
                request.imagePath,
                ethnicHint
            );

            if (!result.success) {
                return {
                    pixelMap: null as unknown as image.PixelMap,
                    elementType: request.elementType,
                    costTime: Date.now() - startTime,
                    success: false,
                    errorMsg: result.errorMsg || '元素提取失败'
                };
            }

            const costTime = Date.now() - startTime;
            console.info(`[EthnicExtractor] 元素提取完成,耗时: ${costTime}ms`);

            // 生成缩略图(用于列表预览)
            let thumbnail: image.PixelMap | undefined;
            if (request.generateThumbnail) {
                thumbnail = await result.pixelMap.scale(200, 200);
            }

            return {
                pixelMap: result.pixelMap,
                thumbnail: thumbnail,
                elementType: request.elementType,
                costTime: costTime,
                success: true
            };
        } catch (error) {
            console.error('[EthnicExtractor] 元素提取异常:', JSON.stringify(error));

            return {
                pixelMap: null as unknown as image.PixelMap,
                elementType: request.elementType,
                costTime: Date.now() - startTime,
                success: false,
                errorMsg: '处理异常,请稍后重试'
            };
        }
    }

    /**
     * 批量提取多个元素
     * @param requests - 抠图请求列表
     * @returns 抠图结果列表
     */
    async extractElements(
        requests: EthnicElementExtractRequest[]
    ): Promise<EthnicElementExtractResult[]> {
        // 串行处理,避免NPU过载
        const results: EthnicElementExtractResult[] = [];

        for (const request of requests) {
            const result = await this.extractElement(request);
            results.push(result);
        }

        // 统计成功率
        const successCount = results.filter(r => r.success).length;
        console.info(
            `[EthnicExtractor] 批量提取完成: ${successCount}/${requests.length} 成功`
        );

        return results;
    }
}

export { EthnicElementExtractor, EthnicElementExtractRequest, EthnicElementExtractResult, EthnicElementType };

主体提示(Subject Hint)的作用
通用抠图模式下,AI需要知道"你要抠什么"。如果不说,AI可能会选错主体(比如把背景里的建筑当成主体)。
通过 subjectHint 参数,告诉AI"我要抠的是服饰",AI就能更准确地定位目标区域。
对于民族元素,结合民族名称(如 miao headwear),AI可以利用训练数据中的文化知识,进一步提高识别精度。


步骤2:物体消除——端侧AI移除不需要的物体

2.1 物体消除API概览

物体消除(Object Removal / Image Inpainting)是Core Vision Kit的第二个核心能力。它的原理是:用户标记要移除的区域,AI分析周围像素,用扩散模型生成自然的内容填充空白区域。

处理流程:
原始图片 → 用户涂抹/选择要移除的区域 → 生成掩码图 → AI推理 → 填充后的图片

支持的消除模式

模式 说明 适用场景
手动涂抹(Brush) 用户用手指涂抹要移除的区域 精确移除特定物体
智能检测(Auto Detect) AI自动检测并标记可能的水印/文字 一键去水印
矩形选择(Rect) 用户框选矩形区域 快速移除大面积物体
2.2 物体消除实现
/*
 * 文件用途:物体消除工具类 - 端侧AI移除图片中的不需要物体
 * 创建时间:2026-07-23
 * 兼容环境:HarmonyOS 7 API 26 / 端侧NPU
 * 版本:v1.0
 * 风险提示:大面积消除(超过图片30%)效果可能不理想
 */

import { image } from '@kit.ImageKit';
import { visionCore } from '@kit.CoreVisionKit';

/**
 * 消除区域定义
 */
interface RemovalRegion {
    /** 区域类型 */
    type: 'brush' | 'rect' | 'auto';
    /** 涂抹路径点(brush模式) */
    brushPoints?: Array<{ x: number; y: number; radius: number }>;
    /** 矩形区域(rect模式) */
    rect?: { left: number; top: number; width: number; height: number };
    /** 自动检测类型(auto模式) */
    autoDetectType?: 'watermark' | 'text' | 'small_object';
}

/**
 * 物体消除请求
 */
interface ObjectRemovalRequest {
    /** 原始图片路径 */
    imagePath: string;
    /** 要消除的区域 */
    regions: RemovalRegion[];
    /** 消除强度(1-10,越高填充越激进) */
    strength?: number;
    /** 图片最大处理尺寸 */
    maxSize?: number;
}

/**
 * 物体消除结果
 */
interface ObjectRemovalResult {
    /** 消除后的像素数据 */
    pixelMap: image.PixelMap;
    /** 处理耗时(毫秒) */
    costTime: number;
    /** 是否成功 */
    success: boolean;
    /** 错误信息 */
    errorMsg?: string;
}

/**
 * 物体消除管理器
 * 封装Core Vision Kit的物体消除能力
 */
class ObjectRemovalManager {
    private static instance: ObjectRemovalManager | null = null;
    private isModelReady: boolean = false;
    private modelInitPromise: Promise<void> | null = null;

    static getInstance(): ObjectRemovalManager {
        if (!ObjectRemovalManager.instance) {
            ObjectRemovalManager.instance = new ObjectRemovalManager();
        }
        return ObjectRemovalManager.instance;
    }

    /**
     * 预加载消除模型
     */
    async preloadModel(): Promise<void> {
        if (this.isModelReady) {
            return;
        }

        if (this.modelInitPromise) {
            return this.modelInitPromise;
        }

        this.modelInitPromise = this.initModel();
        await this.modelInitPromise;
        this.isModelReady = true;
        this.modelInitPromise = null;
    }

    private async initModel(): Promise<void> {
        try {
            console.info('[ObjectRemoval] 开始初始化物体消除模型...');
            const startTime = Date.now();

            await visionCore.inpainting.init({
                modelType: 'lama_large',  // LaMa模型,大模型效果好
                enableNpu: true,
                precision: 'float16'
            });

            const costTime = Date.now() - startTime;
            console.info(`[ObjectRemoval] 模型初始化完成,耗时: ${costTime}ms`);
        } catch (error) {
            console.error('[ObjectRemoval] 模型初始化失败:', JSON.stringify(error));
            throw new Error('物体消除模型加载失败');
        }
    }

    /**
     * 执行物体消除
     * @param request - 消除请求
     * @returns 消除结果
     */
    async removeObject(request: ObjectRemovalRequest): Promise<ObjectRemovalResult> {
        const startTime = Date.now();

        try {
            await this.preloadModel();

            // 步骤1:加载原始图片
            const sourceImage = await this.loadImage(request.imagePath);
            const imageInfo = await sourceImage.getImageInfo();

            // 步骤2:生成掩码图(标记要消除的区域)
            const maskPixelMap = await this.generateMask(
                imageInfo.size.width,
                imageInfo.size.height,
                request.regions
            );

            console.info(
                `[ObjectRemoval] 开始消除,区域数: ${request.regions.length},强度: ${request.strength || 5}`
            );

            // 步骤3:执行AI消除
            const inpaintResult = await visionCore.inpainting.inpaint({
                sourceImage: sourceImage,
                maskImage: maskPixelMap,
                strength: request.strength || 5,
                enablePostProcessing: true,  // 启用后处理平滑
                maxSize: request.maxSize || 2048
            });

            // 步骤4:释放资源
            await sourceImage.release();
            await maskPixelMap.release();

            const costTime = Date.now() - startTime;
            console.info(`[ObjectRemoval] 消除完成,耗时: ${costTime}ms`);

            return {
                pixelMap: inpaintResult.pixelMap,
                costTime: costTime,
                success: true
            };
        } catch (error) {
            console.error('[ObjectRemoval] 消除失败:', JSON.stringify(error));

            return {
                pixelMap: null as unknown as image.PixelMap,
                costTime: Date.now() - startTime,
                success: false,
                errorMsg: '物体消除失败,请尝试缩小消除区域'
            };
        }
    }

    /**
     * 一键去除水印(自动检测模式)
     * @param imagePath - 图片路径
     * @returns 消除结果
     */
    async removeWatermark(imagePath: string): Promise<ObjectRemovalResult> {
        return this.removeObject({
            imagePath: imagePath,
            regions: [{
                type: 'auto',
                autoDetectType: 'watermark'
            }],
            strength: 4  // 水印用较低强度,避免过度消除
        });
    }

    /**
     * 生成掩码图
     * 掩码图中的白色区域表示要消除的部分
     */
    private async generateMask(
        width: number,
        height: number,
        regions: RemovalRegion[]
    ): Promise<image.PixelMap> {
        // 创建全黑(不需要消除)的掩码图
        const maskPixelMap = await image.createPixelMap({
            size: { width: width, height: height },
            pixelFormat: image.PixelMapFormat.RGBA_8888,
            editable: true
        });

        // 对于每个消除区域,在掩码图上绘制白色
        for (const region of regions) {
            switch (region.type) {
                case 'brush':
                    if (region.brushPoints) {
                        await this.drawBrushStrokes(maskPixelMap, region.brushPoints);
                    }
                    break;
                case 'rect':
                    if (region.rect) {
                        await this.drawRect(maskPixelMap, region.rect);
                    }
                    break;
                case 'auto':
                    // 自动检测模式不需要绘制掩码,AI会自动识别
                    // 但为了兼容,可以绘制一个全图50%透明度的掩码
                    console.info('[ObjectRemoval] 使用自动检测模式,无需手动掩码');
                    break;
            }
        }

        return maskPixelMap;
    }

    /**
     * 在掩码图上绘制涂抹笔画
     */
    private async drawBrushStrokes(
        mask: image.PixelMap,
        points: Array<{ x: number; y: number; radius: number }>
    ): Promise<void> {
        // 实际实现中会使用Canvas API在PixelMap上绘制
        // 此处简化表示
        console.info(`[ObjectRemoval] 绘制涂抹区域: ${points.length} 个点`);
    }

    /**
     * 在掩码图上绘制矩形区域
     */
    private async drawRect(
        mask: image.PixelMap,
        rect: { left: number; top: number; width: number; height: number }
    ): Promise<void> {
        console.info(
            `[ObjectRemoval] 绘制矩形区域: ${rect.left},${rect.top} ${rect.width}x${rect.height}`
        );
    }

    /**
     * 加载图片
     */
    private async loadImage(filePath: string): Promise<image.PixelMap> {
        const imageSource = image.createImageSource(filePath);
        const pixelMap = await imageSource.createPixelMap();
        await imageSource.release();
        return pixelMap;
    }
}

export { ObjectRemovalManager, ObjectRemovalRequest, ObjectRemovalResult, RemovalRegion };

物体消除的关键技术点

  1. 掩码图(Mask):白色=要消除,黑色=保留。用户涂抹/框选就是在生成掩码
  2. LaMa模型:目前效果最好的图像修复模型之一,擅长处理大面积缺失
  3. 消除强度:强度越高,AI填充越"激进"(可能产生不自然的内容);强度越低,填充越保守(可能残留痕迹)
  4. 自动检测:对于水印/文字,AI可以自动检测并标记,不需要用户手动涂抹

步骤3:图片降噪——端侧AI增强画质

3.1 图片降噪API概览

图片降噪(Image Denoising / Enhancement)是Core Vision Kit的第三个核心能力。它不仅能去除噪点,还能增强画质、提升清晰度。

增强模式 说明 适用场景
降噪(Denoise) 去除噪点,画面更干净 暗光照片、高ISO照片
超分辨率(Super Resolution) 提升分辨率,增强细节 老照片放大、模糊照片
色彩增强(Color Enhancement) 恢复褪色色彩,提升饱和度 老照片修复、色彩还原
综合增强(Full Enhancement) 降噪+超分+色彩,一站式增强 老旧民族历史照片修复
3.2 图片降噪实现
/*
 * 文件用途:图片降噪与增强工具类 - 端侧AI修复老照片、增强画质
 * 创建时间:2026-07-23
 * 兼容环境:HarmonyOS 7 API 26 / 端侧NPU
 * 版本:v1.0
 * 风险提示:超分辨率模式处理大图时耗时较长,建议异步处理
 */

import { image } from '@kit.ImageKit';
import { visionCore } from '@kit.CoreVisionKit';

/**
 * 图片增强模式
 */
type EnhancementMode = 'denoise' | 'super_resolution' | 'color_enhance' | 'full';

/**
 * 图片增强请求
 */
interface ImageEnhancementRequest {
    /** 图片路径 */
    imagePath: string;
    /** 增强模式 */
    mode: EnhancementMode;
    /** 降噪强度(1-10,仅denoise和full模式有效) */
    denoiseStrength?: number;
    /** 超分倍数(2x或4x,仅super_resolution和full模式有效) */
    upscaleFactor?: number;
    /** 色彩增强强度(1-10,仅color_enhance和full模式有效) */
    colorStrength?: number;
}

/**
 * 图片增强结果
 */
interface ImageEnhancementResult {
    /** 增强后的像素数据 */
    pixelMap: image.PixelMap;
    /** 原始尺寸 */
    originalSize: { width: number; height: number };
    /** 增强后尺寸 */
    enhancedSize: { width: number; height: number };
    /** 处理耗时(毫秒) */
    costTime: number;
    /** 是否成功 */
    success: boolean;
    /** 错误信息 */
    errorMsg?: string;
}

/**
 * 图片增强管理器
 * 封装Core Vision Kit的降噪与增强能力
 */
class ImageEnhancementManager {
    private static instance: ImageEnhancementManager | null = null;
    private isModelReady: boolean = false;
    private modelInitPromise: Promise<void> | null = null;

    static getInstance(): ImageEnhancementManager {
        if (!ImageEnhancementManager.instance) {
            ImageEnhancementManager.instance = new ImageEnhancementManager();
        }
        return ImageEnhancementManager.instance;
    }

    /**
     * 预加载增强模型
     */
    async preloadModel(): Promise<void> {
        if (this.isModelReady) {
            return;
        }

        if (this.modelInitPromise) {
            return this.modelInitPromise;
        }

        this.modelInitPromise = this.initModel();
        await this.modelInitPromise;
        this.isModelReady = true;
        this.modelInitPromise = null;
    }

    private async initModel(): Promise<void> {
        try {
            console.info('[ImageEnhancement] 开始初始化图片增强模型...');
            const startTime = Date.now();

            await visionCore.enhancement.init({
                modelType: 'real_esrgan',  // Real-ESRGAN模型,盲超分效果好
                enableNpu: true,
                precision: 'float16'
            });

            const costTime = Date.now() - startTime;
            console.info(`[ImageEnhancement] 模型初始化完成,耗时: ${costTime}ms`);
        } catch (error) {
            console.error('[ImageEnhancement] 模型初始化失败:', JSON.stringify(error));
            throw new Error('图片增强模型加载失败');
        }
    }

    /**
     * 执行图片增强
     * @param request - 增强请求
     * @returns 增强结果
     */
    async enhanceImage(
        request: ImageEnhancementRequest
    ): Promise<ImageEnhancementResult> {
        const startTime = Date.now();

        try {
            await this.preloadModel();

            // 步骤1:加载原始图片
            const sourceImage = await this.loadImage(request.imagePath);
            const sourceInfo = await sourceImage.getImageInfo();
            const originalSize = {
                width: sourceInfo.size.width,
                height: sourceInfo.size.height
            };

            console.info(
                `[ImageEnhancement] 开始增强: 模式=${request.mode},原始尺寸=${originalSize.width}x${originalSize.height}`
            );

            // 步骤2:根据模式执行增强
            let enhancedPixelMap: image.PixelMap;

            switch (request.mode) {
                case 'denoise':
                    enhancedPixelMap = await this.denoise(
                        sourceImage,
                        request.denoiseStrength || 5
                    );
                    break;

                case 'super_resolution':
                    enhancedPixelMap = await this.superResolve(
                        sourceImage,
                        request.upscaleFactor || 2
                    );
                    break;

                case 'color_enhance':
                    enhancedPixelMap = await this.enhanceColor(
                        sourceImage,
                        request.colorStrength || 5
                    );
                    break;

                case 'full':
                    enhancedPixelMap = await this.fullEnhance(
                        sourceImage,
                        {
                            denoiseStrength: request.denoiseStrength || 5,
                            upscaleFactor: request.upscaleFactor || 2,
                            colorStrength: request.colorStrength || 5
                        }
                    );
                    break;

                default:
                    enhancedPixelMap = sourceImage;
            }

            // 步骤3:获取增强后的尺寸
            const enhancedInfo = await enhancedPixelMap.getImageInfo();
            const enhancedSize = {
                width: enhancedInfo.size.width,
                height: enhancedInfo.size.height
            };

            // 步骤4:释放原始图片(如果增强后是不同的对象)
            if (enhancedPixelMap !== sourceImage) {
                await sourceImage.release();
            }

            const costTime = Date.now() - startTime;
            console.info(
                `[ImageEnhancement] 增强完成: ${originalSize.width}x${originalSize.height}${enhancedSize.width}x${enhancedSize.height},耗时: ${costTime}ms`
            );

            return {
                pixelMap: enhancedPixelMap,
                originalSize: originalSize,
                enhancedSize: enhancedSize,
                costTime: costTime,
                success: true
            };
        } catch (error) {
            console.error('[ImageEnhancement] 增强失败:', JSON.stringify(error));

            return {
                pixelMap: null as unknown as image.PixelMap,
                originalSize: { width: 0, height: 0 },
                enhancedSize: { width: 0, height: 0 },
                costTime: Date.now() - startTime,
                success: false,
                errorMsg: '图片增强失败,请确认图片格式正确'
            };
        }
    }

    /**
     * 降噪处理
     */
    private async denoise(
        source: image.PixelMap,
        strength: number
    ): Promise<image.PixelMap> {
        console.info(`[ImageEnhancement] 降噪处理,强度: ${strength}`);

        const result = await visionCore.enhancement.denoise({
            pixelMap: source,
            strength: strength,
            preserveDetails: true  // 保留细节,避免过度平滑
        });

        return result.pixelMap;
    }

    /**
     * 超分辨率处理
     */
    private async superResolve(
        source: image.PixelMap,
        factor: number
    ): Promise<image.PixelMap> {
        console.info(`[ImageEnhancement] 超分辨率处理,倍数: ${factor}x`);

        const result = await visionCore.enhancement.superResolve({
            pixelMap: source,
            scaleFactor: factor,  // 2x或4x
            enhanceFace: true     // 如果图片中含人脸,增强人脸细节
        });

        return result.pixelMap;
    }

    /**
     * 色彩增强
     */
    private async enhanceColor(
        source: image.PixelMap,
        strength: number
    ): Promise<image.PixelMap> {
        console.info(`[ImageEnhancement] 色彩增强,强度: ${strength}`);

        const result = await visionCore.enhancement.colorEnhance({
            pixelMap: source,
            strength: strength,
            autoWhiteBalance: true  // 自动白平衡
        });

        return result.pixelMap;
    }

    /**
     * 综合增强(降噪 + 超分 + 色彩)
     * 一站式处理,适合老照片修复
     */
    private async fullEnhance(
        source: image.PixelMap,
        options: {
            denoiseStrength: number;
            upscaleFactor: number;
            colorStrength: number;
        }
    ): Promise<image.PixelMap> {
        console.info('[ImageEnhancement] 综合增强处理');

        // 处理顺序很重要:先降噪 → 再超分 → 最后色彩增强
        // 如果先超分再降噪,噪点也会被放大

        const result = await visionCore.enhancement.fullEnhance({
            pixelMap: source,
            denoiseStrength: options.denoiseStrength,
            upscaleFactor: options.upscaleFactor,
            colorStrength: options.colorStrength,
            pipeline: ['denoise', 'super_resolution', 'color_enhance']  // 指定处理管线顺序
        });

        return result.pixelMap;
    }

    /**
     * 加载图片
     */
    private async loadImage(filePath: string): Promise<image.PixelMap> {
        const imageSource = image.createImageSource(filePath);
        const pixelMap = await imageSource.createPixelMap();
        await imageSource.release();
        return pixelMap;
    }
}

export { ImageEnhancementManager, ImageEnhancementRequest, ImageEnhancementResult, EnhancementMode };

图片增强的处理管线顺序很重要

  1. 先降噪:去除噪点,否则超分时噪点也会被放大
  2. 再超分:提升分辨率,增强细节
  3. 最后色彩:调整色彩和对比度

如果顺序错了,效果会大打折扣。比如先超分再降噪,噪点已经被放大成"细节"了,降噪就会把真正的细节也抹掉。


步骤4:图像处理结果缓存策略

4.1 为什么需要缓存?

AI图像处理虽然快,但也不是瞬间完成。对于以下场景,缓存至关重要:

  • 同一个图片多次处理:用户可能反复调整参数
  • 列表预览:处理过的图片在列表中显示缩略图
  • 离线场景:没有网络时,缓存的结果可以立即使用
  • 节省NPU资源:避免重复计算
4.2 缓存策略设计
/*
 * 文件用途:图像处理结果缓存管理器
 * 创建时间:2026-07-23
 * 兼容环境:HarmonyOS 7 API 26
 * 版本:v1.0
 * 风险提示:缓存文件占用存储空间,需设置上限并定期清理
 */

import { fileIo } from '@kit.CoreFileKit';
import { image } from '@kit.ImageKit';
import { hashFunc } from '@kit.ArkTS';

/**
 * 缓存条目
 */
interface CacheEntry {
    /** 缓存键(原始图片路径 + 处理参数的哈希) */
    key: string;
    /** 缓存文件路径 */
    filePath: string;
    /** 原始图片路径 */
    sourcePath: string;
    /** 处理类型 */
    processType: 'matting' | 'removal' | 'enhancement';
    /** 处理参数(JSON字符串) */
    params: string;
    /** 创建时间 */
    createTime: number;
    /** 文件大小(字节) */
    fileSize: number;
    /** 最后访问时间 */
    lastAccessTime: number;
}

/**
 * 缓存配置
 */
interface CacheConfig {
    /** 缓存目录 */
    cacheDir: string;
    /** 最大缓存大小(字节,默认100MB) */
    maxCacheSize: number;
    /** 最大缓存条目数 */
    maxCacheCount: number;
    /** 缓存过期时间(毫秒,默认7天) */
    expireTime: number;
}

/**
 * 图像处理结果缓存管理器
 * 负责缓存AI处理结果,避免重复计算
 */
class ImageProcessCacheManager {
    private static instance: ImageProcessCacheManager | null = null;
    private config: CacheConfig;
    private cacheIndex: Map<string, CacheEntry> = new Map();
    private currentCacheSize: number = 0;
    private indexFilePath: string;

    private constructor(config?: Partial<CacheConfig>) {
        this.config = {
            cacheDir: config?.cacheDir || '/data/storage/el2/base/cache/image_process/',
            maxCacheSize: config?.maxCacheSize || 100 * 1024 * 1024,  // 100MB
            maxCacheCount: config?.maxCacheCount || 200,
            expireTime: config?.expireTime || 7 * 24 * 60 * 60 * 1000  // 7天
        };

        this.indexFilePath = this.config.cacheDir + 'cache_index.json';
        this.ensureCacheDir();
        this.loadCacheIndex();
    }

    static getInstance(config?: Partial<CacheConfig>): ImageProcessCacheManager {
        if (!ImageProcessCacheManager.instance) {
            ImageProcessCacheManager.instance = new ImageProcessCacheManager(config);
        }
        return ImageProcessCacheManager.instance;
    }

    /**
     * 生成缓存键
     * 基于原始图片路径和处理参数生成唯一键
     */
    private generateCacheKey(
        sourcePath: string,
        processType: string,
        params: Record<string, Object>
    ): string {
        const raw = `${sourcePath}|${processType}|${JSON.stringify(params)}`;
        return hashFunc.hash(raw, 'sha256');
    }

    /**
     * 检查缓存是否存在
     */
    hasCache(
        sourcePath: string,
        processType: string,
        params: Record<string, Object>
    ): boolean {
        const key = this.generateCacheKey(sourcePath, processType, params);
        const entry = this.cacheIndex.get(key);

        if (!entry) {
            return false;
        }

        // 检查文件是否存在
        try {
            fileIo.accessSync(entry.filePath);
        } catch (error) {
            // 文件不存在,清除索引
            this.cacheIndex.delete(key);
            this.saveCacheIndex();
            return false;
        }

        // 检查是否过期
        if (Date.now() - entry.createTime > this.config.expireTime) {
            this.removeCacheEntry(key);
            return false;
        }

        // 更新最后访问时间
        entry.lastAccessTime = Date.now();
        return true;
    }

    /**
     * 获取缓存结果
     */
    async getCache(
        sourcePath: string,
        processType: string,
        params: Record<string, Object>
    ): Promise<image.PixelMap | null> {
        const key = this.generateCacheKey(sourcePath, processType, params);

        if (!this.hasCache(sourcePath, processType, params)) {
            return null;
        }

        try {
            const entry = this.cacheIndex.get(key)!;
            console.info(`[ImageCache] 命中缓存: ${entry.filePath}`);

            const imageSource = image.createImageSource(entry.filePath);
            const pixelMap = await imageSource.createPixelMap();
            await imageSource.release();

            return pixelMap;
        } catch (error) {
            console.error('[ImageCache] 读取缓存失败:', JSON.stringify(error));
            this.cacheIndex.delete(key);
            this.saveCacheIndex();
            return null;
        }
    }

    /**
     * 保存结果到缓存
     */
    async saveCache(
        sourcePath: string,
        processType: string,
        params: Record<string, Object>,
        pixelMap: image.PixelMap
    ): Promise<void> {
        const key = this.generateCacheKey(sourcePath, processType, params);

        // 确保缓存目录存在
        this.ensureCacheDir();

        // 生成缓存文件路径
        const cacheFileName = `${key}.png`;
        const cacheFilePath = this.config.cacheDir + cacheFileName;

        try {
            // 保存PixelMap为PNG文件
            const packer = image.createImagePacker();
            const packOpts: image.PackingOption = {
                format: 'image/png',
                quality: 90
            };
            const arrayBuffer = await packer.packing(pixelMap, packOpts);
            await packer.release();

            // 写入文件
            const file = fileIo.openSync(cacheFilePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY);
            fileIo.writeSync(file.fd, arrayBuffer);
            fileIo.closeSync(file);

            // 获取文件大小
            const stat = fileIo.statSync(cacheFilePath);
            const fileSize = stat.size;

            // 创建缓存条目
            const entry: CacheEntry = {
                key: key,
                filePath: cacheFilePath,
                sourcePath: sourcePath,
                processType: processType,
                params: JSON.stringify(params),
                createTime: Date.now(),
                fileSize: fileSize,
                lastAccessTime: Date.now()
            };

            // 更新索引
            this.cacheIndex.set(key, entry);
            this.currentCacheSize += fileSize;

            console.info(
                `[ImageCache] 缓存已保存: ${cacheFilePath},大小: ${(fileSize / 1024).toFixed(1)}KB`
            );

            // 检查是否需要清理
            if (this.currentCacheSize > this.config.maxCacheSize ||
                this.cacheIndex.size > this.config.maxCacheCount) {
                await this.evictCache();
            }

            // 保存索引文件
            this.saveCacheIndex();
        } catch (error) {
            console.error('[ImageCache] 缓存保存失败:', JSON.stringify(error));
        }
    }

    /**
     * 缓存淘汰策略
     * 使用LRU(最近最少使用)策略
     */
    private async evictCache(): Promise<void> {
        console.info('[ImageCache] 开始缓存淘汰...');

        // 按最后访问时间排序,最旧的在前
        const entries = Array.from(this.cacheIndex.entries());
        entries.sort((a, b) => a[1].lastAccessTime - b[1].lastAccessTime);

        // 淘汰直到缓存大小降到最大值的80%以下
        const targetSize = this.config.maxCacheSize * 0.8;
        const targetCount = this.config.maxCacheCount * 0.8;

        let removedCount = 0;
        let removedSize = 0;

        for (const [key, entry] of entries) {
            if (this.currentCacheSize <= targetSize &&
                this.cacheIndex.size <= targetCount) {
                break;
            }

            this.removeCacheEntry(key);
            removedCount++;
            removedSize += entry.fileSize;
        }

        console.info(
            `[ImageCache] 淘汰完成: 删除${removedCount}条,释放${(removedSize / 1024).toFixed(1)}KB`
        );
    }

    /**
     * 删除缓存条目
     */
    private removeCacheEntry(key: string): void {
        const entry = this.cacheIndex.get(key);
        if (!entry) {
            return;
        }

        // 删除文件
        try {
            fileIo.unlinkSync(entry.filePath);
        } catch (error) {
            console.error('[ImageCache] 删除缓存文件失败:', JSON.stringify(error));
        }

        // 更新统计
        this.currentCacheSize -= entry.fileSize;
        this.cacheIndex.delete(key);
    }

    /**
     * 清除所有缓存
     */
    async clearAllCache(): Promise<void> {
        console.info('[ImageCache] 清除所有缓存...');

        for (const [key] of this.cacheIndex) {
            this.removeCacheEntry(key);
        }

        this.currentCacheSize = 0;
        this.saveCacheIndex();

        console.info('[ImageCache] 所有缓存已清除');
    }

    /**
     * 清除过期缓存
     */
    async clearExpiredCache(): Promise<void> {
        const now = Date.now();
        const expiredKeys: string[] = [];

        for (const [key, entry] of this.cacheIndex) {
            if (now - entry.createTime > this.config.expireTime) {
                expiredKeys.push(key);
            }
        }

        for (const key of expiredKeys) {
            this.removeCacheEntry(key);
        }

        if (expiredKeys.length > 0) {
            console.info(`[ImageCache] 清除${expiredKeys.length}条过期缓存`);
            this.saveCacheIndex();
        }
    }

    /**
     * 获取缓存统计
     */
    getCacheStats(): { count: number; totalSize: number } {
        return {
            count: this.cacheIndex.size,
            totalSize: this.currentCacheSize
        };
    }

    /**
     * 确保缓存目录存在
     */
    private ensureCacheDir(): void {
        try {
            fileIo.accessSync(this.config.cacheDir);
        } catch (error) {
            fileIo.mkdirSync(this.config.cacheDir, true);
            console.info('[ImageCache] 创建缓存目录:', this.config.cacheDir);
        }
    }

    /**
     * 加载缓存索引
     */
    private loadCacheIndex(): void {
        try {
            const content = fileIo.readTextSync(this.indexFilePath);
            const entries = JSON.parse(content) as CacheEntry[];

            for (const entry of entries) {
                this.cacheIndex.set(entry.key, entry);
                this.currentCacheSize += entry.fileSize;
            }

            console.info(
                `[ImageCache] 加载缓存索引: ${this.cacheIndex.size}条,${(this.currentCacheSize / 1024 / 1024).toFixed(1)}MB`
            );
        } catch (error) {
            console.info('[ImageCache] 缓存索引不存在,将创建新的');
            this.cacheIndex.clear();
            this.currentCacheSize = 0;
        }
    }

    /**
     * 保存缓存索引
     */
    private saveCacheIndex(): void {
        try {
            const entries = Array.from(this.cacheIndex.values());
            const content = JSON.stringify(entries);
            fileIo.writeTextSync(this.indexFilePath, content);
        } catch (error) {
            console.error('[ImageCache] 保存缓存索引失败:', JSON.stringify(error));
        }
    }
}

export { ImageProcessCacheManager, CacheConfig, CacheEntry };

缓存策略要点

  1. LRU淘汰:最近最少使用的缓存优先删除,保留高频使用的缓存
  2. 双上限控制:同时限制缓存条目数和总大小,防止磁盘撑爆
  3. 过期清理:7天未使用的缓存自动清理,避免浪费空间
  4. 索引持久化:缓存索引保存为JSON文件,应用重启后可以恢复
  5. 缓存键设计:基于原始图片路径 + 处理类型 + 处理参数生成哈希,确保相同输入得到相同缓存

步骤5:实战——民族图鉴AI图像编辑页面

5.1 页面设计
AI图像编辑页面
├── 导航栏(返回 + 标题)
├── 图片预览区
│   ├── 原始图片/处理结果(切换查看)
│   └── 处理进度(加载中状态)
├── 功能选择区
│   ├── 智能抠图(人像/通用)
│   ├── 物体消除
│   ├── 图片增强
│   └── 撤消/重做
├── 参数调节区(根据功能动态显示)
│   ├── 抠图模式:精细边缘开关
│   ├── 消除强度:滑块调节
│   └── 增强模式:降噪/超分/色彩
├── 操作按钮区
│   ├── 开始处理
│   ├── 保存结果
│   └── 分享
└── 底部提示(操作说明)
5.2 页面代码实现
/*
 * 文件用途:AI图像编辑页面 - 集成了智能抠图、物体消除、图片增强功能
 * 创建时间:2026-07-23
 * 兼容环境:HarmonyOS 7 API 26 / 端侧NPU
 * 版本:v1.0
 * 风险提示:首次使用时需加载AI模型(约1-3秒),非首次使用即时响应
 */

import { router } from '@kit.ArkUI';
import { image } from '@kit.ImageKit';
import { promptAction } from '@kit.ArkUI';
import { ImageMattingManager, MattingOptions } from '../utils/ImageMattingManager';
import { ObjectRemovalManager, RemovalRegion } from '../utils/ObjectRemovalManager';
import { ImageEnhancementManager, EnhancementMode } from '../utils/ImageEnhancementManager';
import { ImageProcessCacheManager } from '../utils/ImageProcessCacheManager';
import { EthnicElementExtractor, EthnicElementType } from '../utils/EthnicElementExtractor';

/**
 * AI图像编辑功能类型
 */
type EditFunction = 'matting_portrait' | 'matting_general' | 'removal' | 'enhancement';

/**
 * 处理状态
 */
type ProcessStatus = 'idle' | 'processing' | 'done' | 'error';

@Entry
@Component
struct AiImageEditorPage {
    // 图片相关
    @State sourceImagePath: string = '';
    @State resultPixelMap: image.PixelMap | null = null;
    @State previewMode: 'original' | 'result' = 'original';

    // 功能选择
    @State selectedFunction: EditFunction = 'matting_portrait';
    @State processStatus: ProcessStatus = 'idle';
    @State processProgress: number = 0;
    @State processMessage: string = '';

    // 参数设置
    @State enableFineEdge: boolean = true;
    @State elementType: EthnicElementType = 'clothing';
    @State removalStrength: number = 5;
    @State enhancementMode: EnhancementMode = 'full';
    @State denoiseStrength: number = 5;
    @State upscaleFactor: number = 2;
    @State colorStrength: number = 5;

    // 服务实例
    private mattingManager: ImageMattingManager = ImageMattingManager.getInstance();
    private removalManager: ObjectRemovalManager = ObjectRemovalManager.getInstance();
    private enhancementManager: ImageEnhancementManager = ImageEnhancementManager.getInstance();
    private cacheManager: ImageProcessCacheManager = ImageProcessCacheManager.getInstance();
    private ethnicExtractor: EthnicElementExtractor = new EthnicElementExtractor();

    // 处理历史(用于撤消)
    private processHistory: image.PixelMap[] = [];
    private historyIndex: number = -1;

    aboutToAppear(): void {
        // 获取路由参数
        const params = router.getParams() as Record<string, string>;
        if (params && params['imagePath']) {
            this.sourceImagePath = params['imagePath'];
        }

        // 预加载AI模型
        this.preloadModels();
    }

    /**
     * 预加载所有AI模型
     */
    async preloadModels(): Promise<void> {
        try {
            promptAction.showToast({ message: '正在准备AI模型...' });

            await Promise.all([
                this.mattingManager.preloadModel(),
                this.removalManager.preloadModel(),
                this.enhancementManager.preloadModel()
            ]);

            console.info('[AiImageEditor] 所有AI模型加载完成');
        } catch (error) {
            console.error('[AiImageEditor] 模型加载失败:', JSON.stringify(error));
            promptAction.showToast({ message: 'AI模型加载失败,部分功能可能不可用' });
        }
    }

    /**
     * 执行图像处理
     */
    async startProcessing(): Promise<void> {
        if (!this.sourceImagePath) {
            promptAction.showToast({ message: '请先选择图片' });
            return;
        }

        this.processStatus = 'processing';
        this.processProgress = 0;
        this.processMessage = '正在处理中...';

        try {
            let resultPm: image.PixelMap | null = null;

            switch (this.selectedFunction) {
                case 'matting_portrait':
                    resultPm = await this.doPortraitMatting();
                    break;
                case 'matting_general':
                    resultPm = await this.doGeneralMatting();
                    break;
                case 'removal':
                    resultPm = await this.doObjectRemoval();
                    break;
                case 'enhancement':
                    resultPm = await this.doEnhancement();
                    break;
            }

            if (resultPm) {
                this.resultPixelMap = resultPm;
                this.previewMode = 'result';
                this.processStatus = 'done';
                this.processMessage = '处理完成';

                // 保存到处理历史
                this.processHistory.push(resultPm);
                this.historyIndex = this.processHistory.length - 1;
            } else {
                this.processStatus = 'error';
                this.processMessage = '处理失败,请重试';
            }
        } catch (error) {
            console.error('[AiImageEditor] 处理失败:', JSON.stringify(error));
            this.processStatus = 'error';
            this.processMessage = '处理异常,请稍后重试';
        }
    }

    /**
     * 人像抠图
     */
    private async doPortraitMatting(): Promise<image.PixelMap | null> {
        this.processMessage = '正在执行人像抠图...';

        // 检查缓存
        const cacheParams = { mode: 'portrait', fineEdge: this.enableFineEdge };
        const cached = await this.cacheManager.getCache(
            this.sourceImagePath, 'matting', cacheParams
        );
        if (cached) {
            this.processProgress = 100;
            return cached;
        }

        this.processProgress = 30;
        const options: MattingOptions = {
            mode: 'portrait',
            enableFineEdge: this.enableFineEdge,
            enableCache: true
        };

        const result = await this.mattingManager.portraitMatting(this.sourceImagePath, options);
        this.processProgress = 80;

        if (result.success) {
            // 保存到缓存
            await this.cacheManager.saveCache(
                this.sourceImagePath, 'matting', cacheParams, result.pixelMap
            );
            this.processProgress = 100;
            return result.pixelMap;
        }

        promptAction.showToast({ message: result.errorMsg || '人像抠图失败' });
        return null;
    }

    /**
     * 通用抠图(民族元素提取)
     */
    private async doGeneralMatting(): Promise<image.PixelMap | null> {
        this.processMessage = '正在提取民族元素...';

        const cacheParams = { mode: 'general', elementType: this.elementType };
        const cached = await this.cacheManager.getCache(
            this.sourceImagePath, 'matting', cacheParams
        );
        if (cached) {
            this.processProgress = 100;
            return cached;
        }

        this.processProgress = 30;
        const extractResult = await this.ethnicExtractor.extractElement({
            imagePath: this.sourceImagePath,
            ethnicGroup: 'miao',  // 可以从页面参数中获取
            elementType: this.elementType,
            generateThumbnail: false
        });
        this.processProgress = 80;

        if (extractResult.success) {
            await this.cacheManager.saveCache(
                this.sourceImagePath, 'matting', cacheParams, extractResult.pixelMap
            );
            this.processProgress = 100;
            return extractResult.pixelMap;
        }

        promptAction.showToast({ message: extractResult.errorMsg || '元素提取失败' });
        return null;
    }

    /**
     * 物体消除
     */
    private async doObjectRemoval(): Promise<image.PixelMap | null> {
        this.processMessage = '正在消除物体...';

        // 物体消除需要用户先标记区域,这里使用自动检测水印模式作为示例
        const cacheParams = { type: 'removal', strength: this.removalStrength };
        const cached = await this.cacheManager.getCache(
            this.sourceImagePath, 'removal', cacheParams
        );
        if (cached) {
            this.processProgress = 100;
            return cached;
        }

        this.processProgress = 30;
        const result = await this.removalManager.removeWatermark(this.sourceImagePath);
        this.processProgress = 80;

        if (result.success) {
            await this.cacheManager.saveCache(
                this.sourceImagePath, 'removal', cacheParams, result.pixelMap
            );
            this.processProgress = 100;
            return result.pixelMap;
        }

        promptAction.showToast({ message: result.errorMsg || '物体消除失败' });
        return null;
    }

    /**
     * 图片增强
     */
    private async doEnhancement(): Promise<image.PixelMap | null> {
        this.processMessage = '正在增强图片...';

        const cacheParams = {
            mode: this.enhancementMode,
            denoise: this.denoiseStrength,
            upscale: this.upscaleFactor,
            color: this.colorStrength
        };
        const cached = await this.cacheManager.getCache(
            this.sourceImagePath, 'enhancement', cacheParams
        );
        if (cached) {
            this.processProgress = 100;
            return cached;
        }

        this.processProgress = 30;
        const result = await this.enhancementManager.enhanceImage({
            imagePath: this.sourceImagePath,
            mode: this.enhancementMode,
            denoiseStrength: this.denoiseStrength,
            upscaleFactor: this.upscaleFactor,
            colorStrength: this.colorStrength
        });
        this.processProgress = 80;

        if (result.success) {
            await this.cacheManager.saveCache(
                this.sourceImagePath, 'enhancement', cacheParams, result.pixelMap
            );
            this.processProgress = 100;
            return result.pixelMap;
        }

        promptAction.showToast({ message: result.errorMsg || '图片增强失败' });
        return null;
    }

    /**
     * 保存处理结果
     */
    async saveResult(): Promise<void> {
        if (!this.resultPixelMap) {
            promptAction.showToast({ message: '没有可保存的结果' });
            return;
        }

        try {
            // 保存到相册
            const packer = image.createImagePacker();
            const packOpts: image.PackingOption = {
                format: 'image/png',
                quality: 100
            };
            const arrayBuffer = await packer.packing(this.resultPixelMap, packOpts);
            await packer.release();

            // 使用系统保存能力
            const savePath = `/data/storage/el2/base/files/processed_${Date.now()}.png`;
            const file = fileIo.openSync(savePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY);
            fileIo.writeSync(file.fd, arrayBuffer);
            fileIo.closeSync(file);

            promptAction.showToast({ message: '保存成功' });
            console.info('[AiImageEditor] 结果已保存:', savePath);
        } catch (error) {
            console.error('[AiImageEditor] 保存失败:', JSON.stringify(error));
            promptAction.showToast({ message: '保存失败' });
        }
    }

    build() {
        Column() {
            this.buildNavBar();
            this.buildPreviewArea();
            this.buildFunctionSelector();
            this.buildParamControls();
            this.buildActionButtons();
        }
        .width('100%')
        .height('100%')
        .backgroundColor($r('app.color.page_background'))
    }

    @Builder
    buildNavBar(): void {
        Row() {
            Text('←')
                .fontSize(20)
                .onClick(() => router.back())

            Text('AI图像编辑')
                .fontSize(18)
                .fontWeight(FontWeight.Bold)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)

            Text('  ')
                .width(24)
        }
        .width('100%')
        .height(48)
        .padding({ left: 16, right: 16 })
        .alignItems(VerticalAlign.Center)
    }

    @Builder
    buildPreviewArea(): void {
        Stack() {
            if (this.sourceImagePath && this.previewMode === 'original') {
                Image(this.sourceImagePath)
                    .width('100%')
                    .height(350)
                    .objectFit(ImageFit.Contain)
                    .backgroundColor('#1A1A1A')
            }

            if (this.resultPixelMap && this.previewMode === 'result') {
                Image(this.resultPixelMap)
                    .width('100%')
                    .height(350)
                    .objectFit(ImageFit.Contain)
                    .backgroundColor('#1A1A1A')
            }

            // 处理中状态
            if (this.processStatus === 'processing') {
                Column({ space: 12 }) {
                    Text(this.processMessage)
                        .fontSize(14)
                        .fontColor('#FFFFFF')

                    Progress({ value: this.processProgress, total: 100 })
                        .width(200)
                        .color('#FFFFFF')
                }
                .width('100%')
                .height('100%')
                .backgroundColor('rgba(0,0,0,0.6)')
                .justifyContent(FlexAlign.Center)
            }

            // 无图片状态
            if (!this.sourceImagePath) {
                Column({ space: 12 }) {
                    Text('请选择一张图片开始编辑')
                        .fontSize(14)
                        .fontColor($r('app.color.text_hint'))
                }
                .width('100%')
                .height('100%')
                .justifyContent(FlexAlign.Center)
            }
        }
        .width('100%')
        .height(350)
    }

    @Builder
    buildFunctionSelector(): void {
        Row({ space: 8 }) {
            this.buildFunctionTab('人像抠图', 'matting_portrait')
            this.buildFunctionTab('元素抠图', 'matting_general')
            this.buildFunctionTab('物体消除', 'removal')
            this.buildFunctionTab('图片增强', 'enhancement')
        }
        .width('100%')
        .padding({ left: 12, right: 12, top: 12 })
        .justifyContent(FlexAlign.Start)
    }

    @Builder
    buildFunctionTab(label: string, func: EditFunction): void {
        Text(label)
            .fontSize(13)
            .fontColor(this.selectedFunction === func ?
                '#FFFFFF' : $r('app.color.text_secondary'))
            .padding({ left: 16, right: 16, top: 8, bottom: 8 })
            .backgroundColor(this.selectedFunction === func ?
                $r('app.color.brand') : $r('app.color.card_background'))
            .borderRadius(20)
            .onClick(() => {
                this.selectedFunction = func;
                this.resultPixelMap = null;
                this.previewMode = 'original';
            })
    }

    @Builder
    buildParamControls(): void {
        Column({ space: 12 }) {
            if (this.selectedFunction === 'matting_portrait') {
                Row() {
                    Text('精细边缘(发丝级)')
                        .fontSize(14)
                        .fontColor($r('app.color.text_primary'))
                        .layoutWeight(1)

                    Toggle({ type: ToggleType.Switch, isOn: this.enableFineEdge })
                        .onChange((value: boolean) => {
                            this.enableFineEdge = value;
                        })
                }
                .width('100%')
                .padding(16)
            }

            if (this.selectedFunction === 'matting_general') {
                Row() {
                    Text('元素类型')
                        .fontSize(14)
                        .fontColor($r('app.color.text_primary'))
                        .layoutWeight(1)

                    // 简化:使用文本选择器
                    Text(this.getElementTypeLabel())
                        .fontSize(14)
                        .fontColor($r('app.color.brand'))
                        .onClick(() => {
                            // 切换元素类型
                            const types: EthnicElementType[] = [
                                'headdress', 'accessory', 'clothing', 'pattern', 'embroidery'
                            ];
                            const idx = types.indexOf(this.elementType);
                            this.elementType = types[(idx + 1) % types.length];
                        })
                }
                .width('100%')
                .padding(16)
            }

            if (this.selectedFunction === 'removal') {
                Column({ space: 8 }) {
                    Text(`消除强度: ${this.removalStrength}`)
                        .fontSize(14)
                        .fontColor($r('app.color.text_primary'))

                    Slider({
                        value: this.removalStrength,
                        min: 1,
                        max: 10,
                        step: 1
                    })
                        .onChange((value: number) => {
                            this.removalStrength = value;
                        })
                }
                .width('100%')
                .padding(16)
            }

            if (this.selectedFunction === 'enhancement') {
                Column({ space: 8 }) {
                    this.buildEnhancementModeSelector()
                    this.buildSlider('降噪强度', this.denoiseStrength, (v) => { this.denoiseStrength = v; })
                    this.buildSlider('超分倍数', this.upscaleFactor, (v) => { this.upscaleFactor = v; }, 2, 4, 2)
                    this.buildSlider('色彩增强', this.colorStrength, (v) => { this.colorStrength = v; })
                }
                .width('100%')
                .padding(16)
            }
        }
        .width('100%')
        .backgroundColor($r('app.color.card_background'))
        .borderRadius(12)
        .margin({ left: 12, right: 12, top: 8 })
    }

    @Builder
    buildEnhancementModeSelector(): void {
        Row({ space: 8 }) {
            Text('增强模式')
                .fontSize(14)
                .fontColor($r('app.color.text_primary'))
                .layoutWeight(1)

            this.buildModeTab('降噪', 'denoise')
            this.buildModeTab('超分', 'super_resolution')
            this.buildModeTab('色彩', 'color_enhance')
            this.buildModeTab('综合', 'full')
        }
    }

    @Builder
    buildModeTab(label: string, mode: EnhancementMode): void {
        Text(label)
            .fontSize(12)
            .fontColor(this.enhancementMode === mode ? '#FFFFFF' : $r('app.color.text_secondary'))
            .padding({ left: 10, right: 10, top: 4, bottom: 4 })
            .backgroundColor(this.enhancementMode === mode ?
                $r('app.color.brand') : $r('app.color.page_background'))
            .borderRadius(12)
            .onClick(() => { this.enhancementMode = mode; })
    }

    @Builder
    buildSlider(
        label: string,
        value: number,
        onChange: (v: number) => void,
        min: number = 1,
        max: number = 10,
        step: number = 1
    ): void {
        Column({ space: 4 }) {
            Text(`${label}: ${value}`)
                .fontSize(13)
                .fontColor($r('app.color.text_secondary'))

            Slider({ value: value, min: min, max: max, step: step })
                .onChange(onChange)
        }
    }

    @Builder
    buildActionButtons(): void {
        Column({ space: 12 }) {
            // 主操作按钮
            Button(this.processStatus === 'processing' ? '处理中...' : '开始处理')
                .width('100%')
                .height(48)
                .fontSize(16)
                .fontColor('#FFFFFF')
                .backgroundColor($r('app.color.brand'))
                .borderRadius(8)
                .enabled(this.processStatus !== 'processing')
                .onClick(() => this.startProcessing())

            // 结果操作按钮
            if (this.processStatus === 'done') {
                Row({ space: 12 }) {
                    Button('查看原图')
                        .layoutWeight(1)
                        .height(44)
                        .fontSize(14)
                        .backgroundColor($r('app.color.card_background'))
                        .borderRadius(8)
                        .onClick(() => {
                            this.previewMode = this.previewMode === 'original' ?
                                'result' : 'original';
                        })

                    Button('保存结果')
                        .layoutWeight(1)
                        .height(44)
                        .fontSize(14)
                        .backgroundColor($r('app.color.brand'))
                        .fontColor('#FFFFFF')
                        .borderRadius(8)
                        .onClick(() => this.saveResult())
                }
                .width('100%')
            }
        }
        .width('100%')
        .padding({ left: 12, right: 12, top: 16 })
    }

    /**
     * 获取元素类型的中文标签
     */
    private getElementTypeLabel(): string {
        const labels: Record<EthnicElementType, string> = {
            headdress: '头饰',
            accessory: '配饰',
            clothing: '服饰',
            pattern: '纹样',
            embroidery: '刺绣'
        };
        return labels[this.elementType] || '服饰';
    }
}

⚠️ 常见问题与解决方案

问题1:抠图边缘有毛刺/白边

现象
抠出来的图片,边缘有明显的白色毛刺或锯齿。

常见原因及解决方案

原因1:没有开启精细边缘模式
默认的抠图模式对边缘处理比较粗糙。

解决

// 开启精细边缘模式(发丝级精度)
visionCore.segmentation.segment({
    pixelMap: sourceImage,
    mode: 'portrait',
    enableFineEdge: true  // 关键参数
});

原因2:原始图片分辨率太低
低分辨率图片的边缘信息不足,AI难以精确判断。

解决

  • 使用分辨率较高的原始图片(至少720p)
  • 如果图片分辨率低,先进行超分辨率增强再抠图

原因3:背景与前景颜色相近
比如白色衣服在白色背景下,AI难以区分边界。

解决

  • 对原图先做色彩增强,增加前景与背景的对比度
  • 使用 subjectHint 参数给AI更明确的提示

原因4:边缘后处理不足
抠图结果没有经过边缘平滑处理。

解决

// 抠图后额外做边缘平滑
visionCore.segmentation.postProcess({
    pixelMap: segmentationResult.pixelMap,
    edgeSmooth: true,
    edgeSmoothRadius: 2,  // 平滑半径(像素)
    featherEdge: true     // 边缘羽化
});

问题2:物体消除后填充区域不自然

现象
消除物体后,填充的区域看起来很奇怪——颜色不连贯、纹理不匹配、有明显的修补痕迹。

常见原因及解决方案

原因1:消除区域太大
如果消除区域超过图片的30%,AI很难生成自然的填充内容。

解决

  • 缩小消除区域,分多次消除
  • 先消除核心区域,再逐步消除边缘
  • 对于大面积消除,降低预期效果

原因2:消除强度设置不当

  • 强度太低:填充不完整,残留痕迹
  • 强度太高:过度填充,产生不自然的内容

解决

建议强度范围:
- 小物体(<5%面积):强度 3-5
- 中等物体(5-15%面积):强度 5-7
- 大物体(15-30%面积):强度 7-8
- 超过30%:不建议使用,效果很难保证

原因3:背景纹理复杂
复杂的纹理(如花纹、草地、砖墙)难以完美填充。

解决

  • 对于复杂纹理背景,先用模糊工具轻微模糊消除区域周围
  • 然后让AI基于模糊后的背景进行填充
  • 或者接受"不完美",因为人类视觉对复杂纹理的瑕疵不太敏感

原因4:模型选择不当
不同模型对不同场景的适应性不同。

解决

  • LaMa模型:适合大面积填充、自然场景
  • MAT模型:适合纹理复杂的场景
  • 根据场景选择合适的模型

问题3:图片降噪后画面变模糊

现象
降噪后噪点少了,但画面也变模糊了,失去了细节。

常见原因及解决方案

原因1:降噪强度过高
过度降噪会抹掉画面细节。

解决

降噪强度建议:
- 轻微噪点:强度 2-4
- 中等噪点:强度 4-6
- 严重噪点:强度 6-8
- 不建议超过8,否则细节损失严重

原因2:没有开启细节保留
降噪算法默认可能过度平滑。

解决

visionCore.enhancement.denoise({
    pixelMap: source,
    strength: 5,
    preserveDetails: true,     // 关键:保留细节
    detailThreshold: 0.3      // 细节保留阈值
});

原因3:处理管线顺序错误
如果先降噪再超分,降噪抹掉的细节无法恢复。应该先超分再降噪的场景很少见,但需要注意。

解决

  • 标准管线:降噪 → 超分 → 色彩增强
  • 如果原图噪点很少但模糊:跳过降噪,直接超分 → 色彩增强

问题4:处理速度慢,等待时间长

现象
点击处理按钮后,要等好几秒才有结果。

常见原因及解决方案

原因1:模型未预加载
首次使用时需要加载模型,约1-3秒。

解决

// 在应用启动时或页面初始化时预加载
aboutToAppear(): void {
    this.preloadModels();
}

async preloadModels(): Promise<void> {
    await Promise.all([
        ImageMattingManager.getInstance().preloadModel(),
        ObjectRemovalManager.getInstance().preloadModel(),
        ImageEnhancementManager.getInstance().preloadModel()
    ]);
}

原因2:图片太大
大图(4000x3000以上)处理非常慢。

解决

  • 处理前先缩放到合理尺寸(建议1024px以内)
  • 超分辨率模式除外,超分需要原始分辨率
  • 降噪可以先缩放处理再放大回来

原因3:NPU被其他任务占用
后台有其他AI任务在运行。

解决

  • 串行处理,不要同时处理多张图片
  • 使用任务队列,一个一个处理

原因4:没有使用缓存
重复处理同一张图片。

解决

  • 使用缓存管理器,处理前先查缓存
  • 相同输入 + 相同参数 = 直接返回缓存结果

问题5:通用抠图无法准确识别目标

现象
通用抠图模式识别不出想要的目标,或者抠出了错误的物体。

常见原因及解决方案

原因1:没有提供主体提示
AI不知道用户想要抠什么。

解决

// 提供明确的主体提示
visionCore.segmentation.segment({
    pixelMap: sourceImage,
    mode: 'general',
    subjectHint: 'miao silver headdress'  // 明确告诉AI要抠什么
});

原因2:画面中有多个相似物体
比如照片中有多个人,AI不知道该抠哪个。

解决

  • 先用裁剪工具裁剪出目标区域
  • 或者先用矩形框大致标记目标位置
  • 如果API支持,提供目标位置的bounding box

原因3:目标物体太小或太模糊
AI难以识别。

解决

  • 确保目标物体在画面中占比足够大(建议>15%)
  • 图片清晰,光线充足
  • 目标与背景有足够的对比度

📝 本章小结

核心知识点

本文从Core Vision Kit的核心能力讲起,系统介绍了鸿蒙7端侧AI图像编辑的完整实现:

1. Core Vision Kit三大核心能力

  • 智能抠图(人像抠图 + 通用抠图):基于语义分割 + 边缘精修,精度可达发丝级
  • 物体消除(图像修复):基于LaMa扩散模型,智能填充被移除区域
  • 图片降噪(画质增强):基于Real-ESRGAN模型,降噪 + 超分 + 色彩一站式处理

2. 端侧AI的优势

  • 隐私安全:照片不出设备
  • 离线可用:无需网络
  • 响应快速:NPU加速,毫秒级
  • 零服务器成本

3. 图像处理管线设计

  • 输入 → 预处理(缩放/格式转换) → AI处理 → 后处理(边缘平滑/颜色校正) → 缓存 → 输出
  • 处理顺序敏感:降噪 → 超分 → 色彩,顺序不能乱

4. 缓存策略

  • LRU淘汰策略,双上限控制(条目数 + 总大小)
  • 缓存键基于原始图片路径 + 处理类型 + 处理参数
  • 索引持久化,应用重启后可恢复
  • 自动清理过期缓存

5. 「民族图鉴」应用场景

  • 民族服饰抠图:提取银饰、腰带、帽子等元素,制作民族特色头像
  • 文物照片去水印:清除照片中的水印、杂物、反光
  • 历史老照片修复:增强模糊、褪色的民族老照片

最佳实践总结

模型预加载是必须的

首次抠图慢是因为模型加载(1-3秒)。
在应用启动时预加载模型,首次抠图也能即时响应。
用户体验天差地别。

缓存是体验的关键

同一张图片 + 相同参数 = 缓存命中。
不要重复计算,节省NPU资源,提升响应速度。
LRU淘汰 + 过期清理,防止磁盘撑爆。

合适的图片尺寸很重要

大图处理慢,但小图质量差。
预处理缩放到1024px是平衡点。
超分模式除外——超分需要原始分辨率的更多信息。

参数调优要循序渐进

降低强度从低到高试,找到最佳值。
- 降噪:强度太高会模糊
- 消除:强度太高会产生不自然内容
- 抠图:精细边缘开关影响速度

合理管理用户预期

AI不是万能的:
- 大面积消除效果可能不完美
- 复杂背景抠图可能有瑕疵
- 严重模糊的老照片修复有限
在产品上做好说明,用户就不会失望。

降级方案必不可少

不是所有设备都支持端侧AI。
- 检测设备能力
- 不支持的设备:隐藏AI功能入口
- 支持的设备:提供完整功能
- 处理失败:提供友好的错误提示和重试

下一步预告

在下一篇文章中,我们将:

  • 学习跨设备流转的核心概念与鸿蒙分布式能力
  • 理解分布式软总线、分布式数据管理的原理
  • 掌握手机与平板无缝接续阅读的实现
  • 实现「民族图鉴」在手机和平板间的流畅切换
  • 学习跨设备状态同步与数据一致性保障
  • 解决连接超时、数据冲突、设备兼容等常见问题

🔗 相关链接

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐