鸿蒙跨端智能相册分类系统开发指南 原创

进修的泡芙
发布于 2025-6-22 17:28
浏览
0收藏

鸿蒙跨端智能相册分类系统开发指南

一、系统架构设计

基于HarmonyOS的AI能力和分布式技术,构建智能相册分类系统:
图像处理层:识别照片中的人物和场景

分类管理层:管理照片分类和标签

跨端同步层:多设备间同步分类结果和相册状态

用户界面层:展示分类结果和提供交互

!https://example.com/harmony-photo-classifier-arch.png

二、核心代码实现
图像分类服务

// PhotoClassifierService.ets
import ai from ‘@ohos.ai’;
import distributedData from ‘@ohos.distributedData’;
import { Photo, Person, Scene, Album } from ‘./PhotoTypes’;

class PhotoClassifierService {
private static instance: PhotoClassifierService = null;
private modelManager: ai.ModelManager;
private dataManager: distributedData.DataManager;
private listeners: ClassifierListener[] = [];

private constructor() {
this.initModelManager();
this.initDataManager();
public static getInstance(): PhotoClassifierService {

if (!PhotoClassifierService.instance) {
  PhotoClassifierService.instance = new PhotoClassifierService();

return PhotoClassifierService.instance;

private initModelManager(): void {

try {
  this.modelManager = ai.createModelManager(getContext());
  
  // 加载人脸识别模型
  this.modelManager.loadModel({
    modelName: 'face_recognition',
    modelPath: 'resources/rawfile/face_recognition.model',
    callback: (err, data) => {
      if (err) {
        console.error('加载人脸识别模型失败:', JSON.stringify(err));

}

  });
  
  // 加载场景识别模型
  this.modelManager.loadModel({
    modelName: 'scene_recognition',
    modelPath: 'resources/rawfile/scene_recognition.model',
    callback: (err, data) => {
      if (err) {
        console.error('加载场景识别模型失败:', JSON.stringify(err));

}

  });

catch (err) {

  console.error('初始化模型管理器失败:', JSON.stringify(err));

}

private initDataManager(): void {
this.dataManager = distributedData.createDataManager({
bundleName: ‘com.example.photoclassifier’,
area: distributedData.Area.GLOBAL,
isEncrypted: true
});

this.dataManager.registerDataListener('photo_sync', (data) => {
  this.handleSyncData(data);
});

public async requestPermissions(): Promise<boolean> {

try {
  const permissions = [
    'ohos.permission.USE_AI',
    'ohos.permission.READ_MEDIA',
    'ohos.permission.DISTRIBUTED_DATASYNC'
  ];
  
  const result = await abilityAccessCtrl.requestPermissionsFromUser(
    getContext(), 
    permissions
  );
  
  return result.grantedPermissions.length === permissions.length;

catch (err) {

  console.error('请求权限失败:', JSON.stringify(err));
  return false;

}

public async classifyPhoto(imageData: ArrayBuffer): Promise<Photo> {
try {
// 识别照片中的人物
const faceInput = {
data: imageData,
width: 224,
height: 224,
format: ‘RGB’
};

  const faceOutput = await this.modelManager.runModel({
    modelName: 'face_recognition',
    input: faceInput
  });
  
  // 识别照片场景
  const sceneInput = {
    data: imageData,
    width: 224,
    height: 224,
    format: 'RGB'
  };
  
  const sceneOutput = await this.modelManager.runModel({
    modelName: 'scene_recognition',
    input: sceneInput
  });
  
  const photo: Photo = {
    id: Date.now().toString(),
    imageData: imageData,
    people: faceOutput.result.faces.map(face => ({
      id: face.id,
      name: '未知',
      confidence: face.confidence,
      boundingBox: face.boundingBox
    })),
    scene: {
      type: sceneOutput.result.scene,
      confidence: sceneOutput.result.confidence
    },
    timestamp: Date.now()
  };
  
  // 同步分类结果
  this.syncPhoto(photo);
  
  return photo;

catch (err) {

  console.error('照片分类失败:', JSON.stringify(err));
  throw err;

}

public async createAlbum(name: string, coverPhotoId: string): Promise<Album> {
const album: Album = {
id: Date.now().toString(),
name: name,
coverPhotoId: coverPhotoId,
photoIds: [],
createdAt: Date.now()
};

this.syncAlbum(album);
return album;

public async addPhotoToAlbum(photoId: string, albumId: string): Promise<void> {

this.dataManager.syncData('album_update', {
  type: 'add_photo',
  data: {
    photoId: photoId,
    albumId: albumId
  },
  timestamp: Date.now()
});

public async renamePerson(personId: string, name: string): Promise<void> {

this.dataManager.syncData('person_update', {
  type: 'rename_person',
  data: {
    personId: personId,
    name: name
  },
  timestamp: Date.now()
});

private syncPhoto(photo: Photo): void {

this.dataManager.syncData('photo_sync', {
  type: 'photo_classified',
  data: photo,
  timestamp: Date.now()
});

private syncAlbum(album: Album): void {

this.dataManager.syncData('album_sync', {
  type: 'album_created',
  data: album,
  timestamp: Date.now()
});

private handleSyncData(data: any): void {

if (!data) return;

switch (data.type) {
  case 'photo_classified':
    this.notifyPhotoClassified(data.data);
    break;
  case 'album_created':
    this.notifyAlbumCreated(data.data);
    break;
  case 'add_photo':
    this.notifyPhotoAddedToAlbum(data.data);
    break;
  case 'rename_person':
    this.notifyPersonRenamed(data.data);
    break;

}

private notifyPhotoClassified(photo: Photo): void {
this.listeners.forEach(listener => {
listener.onPhotoClassified?.(photo);
});
private notifyAlbumCreated(album: Album): void {

this.listeners.forEach(listener => {
  listener.onAlbumCreated?.(album);
});

private notifyPhotoAddedToAlbum(data: { photoId: string, albumId: string }): void {

this.listeners.forEach(listener => {
  listener.onPhotoAddedToAlbum?.(data.photoId, data.albumId);
});

private notifyPersonRenamed(data: { personId: string, name: string }): void {

this.listeners.forEach(listener => {
  listener.onPersonRenamed?.(data.personId, data.name);
});

public addListener(listener: ClassifierListener): void {

if (!this.listeners.includes(listener)) {
  this.listeners.push(listener);

}

public removeListener(listener: ClassifierListener): void {
this.listeners = this.listeners.filter(l => l !== listener);
}

interface ClassifierListener {
onPhotoClassified?(photo: Photo): void;
onAlbumCreated?(album: Album): void;
onPhotoAddedToAlbum?(photoId: string, albumId: string): void;
onPersonRenamed?(personId: string, name: string): void;
export const photoClassifierService = PhotoClassifierService.getInstance();

相册管理界面

// AlbumScreen.ets
import { photoClassifierService } from ‘./PhotoClassifierService’;
import { Photo, Person, Album } from ‘./PhotoTypes’;

@Component
export struct AlbumScreen {
@State hasPermission: boolean = false;
@State isProcessing: boolean = false;
@State photos: Photo[] = [];
@State people: Person[] = [];
@State albums: Album[] = [];
@State selectedTab: ‘photos’ ‘people’
‘albums’ = ‘photos’;
@State showAlbumDialog: boolean = false;
@State newAlbumName: string = ‘’;
@State selectedPhotos: string[] = [];

aboutToAppear() {
this.checkPermissions();
photoClassifierService.addListener({
onPhotoClassified: (photo) => {
this.handlePhotoClassified(photo);
},
onAlbumCreated: (album) => {
this.handleAlbumCreated(album);
},
onPhotoAddedToAlbum: (photoId, albumId) => {
this.handlePhotoAddedToAlbum(photoId, albumId);
},
onPersonRenamed: (personId, name) => {
this.handlePersonRenamed(personId, name);
});

aboutToDisappear() {

photoClassifierService.removeListener({
  onPhotoClassified: (photo) => {
    this.handlePhotoClassified(photo);
  },
  onAlbumCreated: (album) => {
    this.handleAlbumCreated(album);
  },
  onPhotoAddedToAlbum: (photoId, albumId) => {
    this.handlePhotoAddedToAlbum(photoId, albumId);
  },
  onPersonRenamed: (personId, name) => {
    this.handlePersonRenamed(personId, name);

});

build() {

Column() {
  // 标题栏
  Row() {
    Text('智能相册')
      .fontSize(24)
      .fontWeight(FontWeight.Bold)
      .layoutWeight(1)
    
    Button(this.hasPermission ? '添加照片' : '授权')
      .width(100)
      .onClick(() => {
        if (this.hasPermission) {
          this.pickPhotos();

else {

          this.requestPermissions();

})

.padding(10)

  .width('100%')
  
  // 标签栏
  Tabs({ barPosition: BarPosition.Start }) {
    TabContent() {
      // 照片视图
      this.buildPhotosView()
    }.tabBar('照片')
    
    TabContent() {
      // 人物视图
      this.buildPeopleView()
    }.tabBar('人物')
    
    TabContent() {
      // 相册视图
      this.buildAlbumsView()
    }.tabBar('相册')

.index(0)

  .vertical(false)
  .barWidth('100%')
  .barHeight(40)
  .onChange((index: number) => {
    this.selectedTab = ['photos', 'people', 'albums'][index] as 'photos' 'people'

‘albums’;
})
.width(‘100%’)

.height('100%')
.padding(20)

// 创建相册对话框
if (this.showAlbumDialog) {
  DialogComponent({
    title: '新建相册',
    content: this.buildAlbumDialogContent(),
    confirm: {
      value: '创建',
      action: () => this.createAlbum()
    },
    cancel: {
      value: '取消',
      action: () => this.showAlbumDialog = false

})

}

private buildPhotosView(): void {
if (this.photos.length === 0) {
Column() {
Text(‘暂无照片’)
.fontSize(18)
.margin({ bottom: 10 })

    Text('点击"添加照片"按钮导入照片')
      .fontSize(16)
      .fontColor('#666666')

.padding(20)

  .width('90%')
  .backgroundColor('#F5F5F5')
  .borderRadius(8)
  .margin({ top: 50 })

else {

  Grid() {
    ForEach(this.photos, (photo) => {
      GridItem() {
        Stack() {
          Image(photo.imageData)
            .width('100%')
            .height(150)
            .objectFit(ImageFit.Cover)
          
          if (this.selectedPhotos.includes(photo.id)) {
            Image($r('app.media.ic_check'))
              .width(24)
              .height(24)
              .position({ x: '85%', y: '85%' })

}

        .borderRadius(8)
        .onClick(() => {
          this.togglePhotoSelection(photo.id);
        })

})

.columnsTemplate(‘1fr 1fr 1fr’)

  .rowsGap(10)
  .columnsGap(10)
  .margin({ top: 10 })
  
  if (this.selectedPhotos.length > 0) {
    Row() {
      Button('添加到相册')
        .width(150)
        .height(50)
        .fontSize(18)
        .onClick(() => {
          this.showAlbumDialog = true;
        })
      
      Button('取消选择')
        .width(150)
        .height(50)
        .fontSize(18)
        .margin({ left: 20 })
        .onClick(() => {
          this.selectedPhotos = [];
        })

.margin({ top: 20 })

}

private buildPeopleView(): void {

if (this.people.length === 0) {
  Column() {
    Text('未识别到人物')
      .fontSize(18)
      .margin({ bottom: 10 })
    
    Text('请添加包含人物的照片')
      .fontSize(16)
      .fontColor('#666666')

.padding(20)

  .width('90%')
  .backgroundColor('#F5F5F5')
  .borderRadius(8)
  .margin({ top: 50 })

else {

  List({ space: 10 }) {
    ForEach(this.people, (person) => {
      ListItem() {
        Row() {
          Image(person.faceImage)
            .width(60)
            .height(60)
            .borderRadius(30)
            .margin({ right: 15 })
          
          Text(person.name)
            .fontSize(16)
            .layoutWeight(1)
          
          Text(${person.photoCount}张照片)
            .fontSize(14)
            .fontColor('#666666')

.padding(10)

        .width('100%')

.onClick(() => {

        this.showPersonPhotos(person.id);
      })
    })

.height(‘80%’)

}

private buildAlbumsView(): void {
if (this.albums.length === 0) {
Column() {
Text(‘暂无相册’)
.fontSize(18)
.margin({ bottom: 10 })

    Text('选择照片后可以创建相册')
      .fontSize(16)
      .fontColor('#666666')

.padding(20)

  .width('90%')
  .backgroundColor('#F5F5F5')
  .borderRadius(8)
  .margin({ top: 50 })

else {

  List({ space: 10 }) {
    ForEach(this.albums, (album) => {
      ListItem() {
        Row() {
          Image(this.getAlbumCover(album))
            .width(80)
            .height(80)
            .borderRadius(8)
            .margin({ right: 15 })
          
          Column() {
            Text(album.name)
              .fontSize(16)
              .fontWeight(FontWeight.Bold)
              .margin({ bottom: 5 })
            
            Text(${album.photoIds.length}张照片)
              .fontSize(14)
              .fontColor('#666666')

.layoutWeight(1)

.padding(10)

        .width('100%')

.onClick(() => {

        this.showAlbumPhotos(album.id);
      })
    })

.height(‘80%’)

}

private buildAlbumDialogContent(): void {
Column() {
TextInput({ placeholder: ‘输入相册名称’, text: this.newAlbumName })
.onChange((value: string) => {
this.newAlbumName = value;
})

  if (this.selectedPhotos.length > 0) {
    Text(将添加${this.selectedPhotos.length}张照片到相册)
      .fontSize(14)
      .fontColor('#666666')
      .margin({ top: 10 })

}

.padding(20)
.width('100%')

private getAlbumCover(album: Album): Resource {

const coverPhoto = this.photos.find(p => p.id === album.coverPhotoId);
return coverPhoto ? coverPhoto.imageData : $r('app.media.ic_default_album');

private async checkPermissions(): Promise<void> {

try {
  const permissions = [
    'ohos.permission.USE_AI',
    'ohos.permission.READ_MEDIA',
    'ohos.permission.DISTRIBUTED_DATASYNC'
  ];
  
  const result = await abilityAccessCtrl.verifyPermissions(
    getContext(),
    permissions
  );
  
  this.hasPermission = result.every(perm => perm.granted);

catch (err) {

  console.error('检查权限失败:', JSON.stringify(err));
  this.hasPermission = false;

}

private async requestPermissions(): Promise<void> {
this.hasPermission = await photoClassifierService.requestPermissions();

if (!this.hasPermission) {
  prompt.showToast({ message: '授权失败,无法访问照片' });

}

private async pickPhotos(): Promise<void> {
try {
const picker = new photo.Picker();
const result = await picker.select({
type: photo.PickerType.IMAGE,
maxSelectNumber: 20
});

  if (result.photoUris.length === 0) return;
  
  this.isProcessing = true;
  
  for (const uri of result.photoUris) {
    const file = await fileIo.open(uri, fileIo.OpenMode.READ_ONLY);
    const buffer = await fileIo.read(file.fd, { length: 0 });
    await fileIo.close(file.fd);
    
    const photo = await photoClassifierService.classifyPhoto(buffer.buffer);
    this.photos = [...this.photos, photo];

} catch (err) {

  console.error('选择照片失败:', JSON.stringify(err));
  prompt.showToast({ message: '选择照片失败,请重试' });

finally {

  this.isProcessing = false;

}

private togglePhotoSelection(photoId: string): void {
if (this.selectedPhotos.includes(photoId)) {
this.selectedPhotos = this.selectedPhotos.filter(id => id !== photoId);
else {

  this.selectedPhotos = [...this.selectedPhotos, photoId];

}

private async createAlbum(): Promise<void> {
if (!this.newAlbumName.trim()) {
prompt.showToast({ message: ‘请输入相册名称’ });
return;
if (this.selectedPhotos.length === 0) {

  prompt.showToast({ message: '请选择要添加到相册的照片' });
  return;

try {

  const album = await photoClassifierService.createAlbum(
    this.newAlbumName,
    this.selectedPhotos[0]
  );
  
  for (const photoId of this.selectedPhotos) {
    await photoClassifierService.addPhotoToAlbum(photoId, album.id);

this.albums = […this.albums, album];

  this.selectedPhotos = [];
  this.showAlbumDialog = false;
  this.newAlbumName = '';

catch (err) {

  console.error('创建相册失败:', JSON.stringify(err));
  prompt.showToast({ message: '创建相册失败,请重试' });

}

private handlePhotoClassified(photo: Photo): void {
this.photos = […this.photos, photo];

// 更新人物列表
photo.people.forEach(person => {
  const existingPerson = this.people.find(p => p.id === person.id);
  if (existingPerson) {
    existingPerson.photoCount += 1;

else {

    this.people = [...this.people, {
      id: person.id,
      name: person.name,
      faceImage: this.extractFaceImage(photo.imageData, person.boundingBox),
      photoCount: 1
    }];

});

private extractFaceImage(imageData: ArrayBuffer, boundingBox: any): ArrayBuffer {

// 实际应用中应实现从原图中裁剪人脸区域
return imageData; // 简化为返回原图

private handleAlbumCreated(album: Album): void {

this.albums = [...this.albums, album];

private handlePhotoAddedToAlbum(photoId: string, albumId: string): void {

const album = this.albums.find(a => a.id === albumId);
if (album && !album.photoIds.includes(photoId)) {
  album.photoIds = [...album.photoIds, photoId];

}

private handlePersonRenamed(personId: string, name: string): void {
const person = this.people.find(p => p.id === personId);
if (person) {
person.name = name;
}

private showPersonPhotos(personId: string): void {
// 导航到人物照片列表页面
router.push({
url: ‘pages/PersonPhotos’,
params: { personId: personId }
});
private showAlbumPhotos(albumId: string): void {

// 导航到相册照片列表页面
router.push({
  url: 'pages/AlbumPhotos',
  params: { albumId: albumId }
});

}

类型定义

// PhotoTypes.ets
export interface Photo {
id: string;
imageData: ArrayBuffer;
people: {
id: string;
name: string;
confidence: number;
boundingBox: any;
}[];
scene: {
type: string;
confidence: number;
};
timestamp: number;
export interface Person {

id: string;
name: string;
faceImage: ArrayBuffer;
photoCount: number;
export interface Album {

id: string;
name: string;
coverPhotoId: string;
photoIds: string[];
createdAt: number;

三、项目配置与权限
权限配置

// module.json5
“module”: {

"requestPermissions": [

“name”: “ohos.permission.USE_AI”,

    "reason": "使用AI模型识别照片内容"
  },

“name”: “ohos.permission.READ_MEDIA”,

    "reason": "访问相册照片"
  },

“name”: “ohos.permission.DISTRIBUTED_DATASYNC”,

    "reason": "同步分类结果"

],

"abilities": [

“name”: “MainAbility”,

    "type": "page",
    "visible": true

]

}

四、总结与扩展

本智能相册分类系统实现了以下核心功能:
智能分类:自动识别照片中的人物和场景

人物管理:聚合同一人物的所有照片

相册创建:支持自定义相册分类

跨设备同步:多设备间同步分类结果和相册

扩展方向:
场景相册:自动创建旅行、家庭聚会等场景相册

智能搜索:通过自然语言搜索照片

回忆功能:按时间线展示历史照片

共享相册:与家人朋友共享特定相册

照片增强:自动优化照片质量

云备份:将照片备份到云端

通过HarmonyOS的分布式技术,我们构建了一个智能化的相册管理系统,能够自动整理照片并在多设备间同步分类结果,大大提升了照片管理的效率和体验。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
收藏
回复
举报
回复
    相关推荐