HarmonyOS应用《民族图鉴》开发第78篇:智能抠图与图片增强——端侧AI图像编辑实战

📖 引言
想象一下这个场景:你在「民族图鉴」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 };
关键设计点:
- 单例模式:全局只有一个抠图管理器,避免重复加载模型
- 模型预加载:应用启动时预加载模型,首次抠图无延迟
- 图片预处理:大图先缩放到1024px以内再处理,速度提升显著
- 精细边缘模式:人像抠图开启发丝级精度,通用抠图可以关闭以提速
- 资源释放:处理完及时释放中间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 };
物体消除的关键技术点:
- 掩码图(Mask):白色=要消除,黑色=保留。用户涂抹/框选就是在生成掩码
- LaMa模型:目前效果最好的图像修复模型之一,擅长处理大面积缺失
- 消除强度:强度越高,AI填充越"激进"(可能产生不自然的内容);强度越低,填充越保守(可能残留痕迹)
- 自动检测:对于水印/文字,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 };
图片增强的处理管线顺序很重要:
- 先降噪:去除噪点,否则超分时噪点也会被放大
- 再超分:提升分辨率,增强细节
- 最后色彩:调整色彩和对比度
如果顺序错了,效果会大打折扣。比如先超分再降噪,噪点已经被放大成"细节"了,降噪就会把真正的细节也抹掉。
步骤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 };
缓存策略要点:
- LRU淘汰:最近最少使用的缓存优先删除,保留高频使用的缓存
- 双上限控制:同时限制缓存条目数和总大小,防止磁盘撑爆
- 过期清理:7天未使用的缓存自动清理,避免浪费空间
- 索引持久化:缓存索引保存为JSON文件,应用重启后可以恢复
- 缓存键设计:基于原始图片路径 + 处理类型 + 处理参数生成哈希,确保相同输入得到相同缓存
步骤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功能入口
- 支持的设备:提供完整功能
- 处理失败:提供友好的错误提示和重试
下一步预告
在下一篇文章中,我们将:
- 学习跨设备流转的核心概念与鸿蒙分布式能力
- 理解分布式软总线、分布式数据管理的原理
- 掌握手机与平板无缝接续阅读的实现
- 实现「民族图鉴」在手机和平板间的流畅切换
- 学习跨设备状态同步与数据一致性保障
- 解决连接超时、数据冲突、设备兼容等常见问题
🔗 相关链接
- 项目源码: GitCode 仓库
- Core Vision Kit 官方文档: HarmonyOS 开发者文档
- 图像分割技术: Semantic Segmentation Overview
- LaMa 图像修复: Resolution-robust Large Mask Inpainting
- Real-ESRGAN: Real-World Blind Super-Resolution
更多推荐




所有评论(0)