鸿蒙跨端植物识别园艺助手开发指南 原创

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

鸿蒙跨端植物识别园艺助手开发指南

一、项目概述

本指南基于HarmonyOS的分布式能力和AI图像识别技术,开发一款智能植物识别园艺助手应用。该系统能够通过设备摄像头识别植物种类,获取植物养护知识,并将识别结果同步到多设备,借鉴了《鸿蒙跨端U同步》中多设备数据同步的技术原理。

二、系统架构

±--------------------+ ±--------------------+ ±--------------------+
主设备 <-----> 分布式数据总线 <-----> 从设备
(手机/平板) (Distributed Bus) (智能手表/其他设备)
±---------±---------+ ±---------±---------+ ±---------±---------+

±---------v----------+ ±---------v----------+ ±---------v----------+
图像识别模块 植物知识模块 数据同步模块
(Image Recognition) (Plant Knowledge) (Data Sync)

±--------------------+ ±--------------------+ ±--------------------+

三、核心代码实现
植物识别服务

// src/main/ets/service/PlantService.ts
import { distributedData } from ‘@ohos.data.distributedData’;
import { BusinessError } from ‘@ohos.base’;
import { camera } from ‘@ohos.multimedia.camera’;
import { image } from ‘@ohos.multimedia.image’;
import { http } from ‘@ohos.net.http’;
import { imageClassification } from ‘@ohos.ai.imageClassification’;

interface PlantInfo {
id: string;
name: string;
scientificName: string;
confidence: number;
timestamp: number;
imageUri: string;
interface PlantDetail {

id: string;
watering: string;
sunlight: string;
soil: string;
temperature: string;
fertilization: string;
pruning: string;
export class PlantService {

private static instance: PlantService;
private kvStore: distributedData.KVStore | null = null;
private readonly STORE_ID = ‘plant_data_store’;
private cameraInput: camera.CameraInput | null = null;
private previewOutput: camera.PreviewOutput | null = null;
private imageClassifier: imageClassification.ImageClassifier | null = null;
private httpRequest = http.createHttp();
private plantHistory: PlantInfo[] = [];
private readonly API_KEY = ‘YOUR_PLANT_API_KEY’; // 替换为实际API密钥
private readonly API_URL = ‘https://plant-api.example.com/v1/identify’;

private constructor() {
this.initKVStore();
this.initCamera();
this.initImageClassifier();
public static getInstance(): PlantService {

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

return PlantService.instance;

private async initKVStore(): Promise<void> {

try {
  const options: distributedData.KVManagerConfig = {
    bundleName: 'com.example.plantidentifier',
    userInfo: {
      userId: '0',
      userType: distributedData.UserType.SAME_USER_ID

};

  const kvManager = distributedData.createKVManager(options);
  this.kvStore = await kvManager.getKVStore({
    storeId: this.STORE_ID,
    options: {
      createIfMissing: true,
      encrypt: false,
      backup: false,
      autoSync: true,
      kvStoreType: distributedData.KVStoreType.SINGLE_VERSION

});

  this.kvStore.on('dataChange', distributedData.SubscribeType.SUBSCRIBE_TYPE_REMOTE, (data) => {
    data.insertEntries.forEach((entry: distributedData.Entry) => {
      if (entry.key === 'plant_history') {
        this.notifyHistoryChange(entry.value.value as PlantInfo[]);

});

  });

catch (e) {

  console.error(Failed to initialize KVStore. Code: {e.code}, message: {e.message});

}

private async initCamera(): Promise<void> {
try {
const cameraManager = camera.getCameraManager();
const cameras = cameraManager.getSupportedCameras();
if (cameras.length === 0) {
console.error(‘No camera available’);
return;
// 使用后置摄像头

  this.cameraInput = cameraManager.createCameraInput(cameras[0]);
  await this.cameraInput.open();
  
  // 创建预览输出
  const surfaceId = 'previewSurface';
  this.previewOutput = cameraManager.createPreviewOutput(surfaceId);
  
  // 创建会话并开始预览
  const captureSession = cameraManager.createCaptureSession();
  await captureSession.beginConfig();
  await captureSession.addInput(this.cameraInput);
  await captureSession.addOutput(this.previewOutput);
  await captureSession.commitConfig();
  await captureSession.start();

catch (e) {

  console.error(Failed to initialize camera. Code: {e.code}, message: {e.message});

}

private async initImageClassifier(): Promise<void> {
try {
const config: imageClassification.ImageClassifierConfig = {
modelType: imageClassification.ModelType.PLANT, // 植物识别模型
scoreThreshold: 0.5
};

  this.imageClassifier = await imageClassification.createImageClassifier(config);

catch (e) {

  console.error(Failed to initialize image classifier. Code: {e.code}, message: {e.message});

}

public async identifyPlant(imageObj: image.Image): Promise<PlantInfo | null> {
if (!this.imageClassifier) return null;

try {
  // 本地AI识别
  const localResult = await this.localIdentify(imageObj);
  
  // 如果本地识别置信度低,调用云端API
  if (localResult.confidence < 0.7) {
    const cloudResult = await this.cloudIdentify(imageObj);
    if (cloudResult) return cloudResult;

return localResult;

catch (e) {

  console.error(Failed to identify plant. Code: {e.code}, message: {e.message});
  return null;

}

private async localIdentify(imageObj: image.Image): Promise<PlantInfo> {
if (!this.imageClassifier) throw new Error(‘Image classifier not initialized’);

const results = await this.imageClassifier.classify(imageObj);
const bestMatch = results[0];

return {
  id: local_${Date.now()},
  name: bestMatch.className,
  scientificName: '', // 本地模型可能不提供学名
  confidence: bestMatch.score,
  timestamp: Date.now(),
  imageUri: '' // 实际应用中应保存图片路径
};

private async cloudIdentify(imageObj: image.Image): Promise<PlantInfo | null> {

try {
  // 将图像转换为Base64
  const imageData = await this.imageToBase64(imageObj);
  
  // 调用云端植物识别API
  const response = await this.httpRequest.request(
    this.API_URL,

method: http.RequestMethod.POST,

      header: {
        'Content-Type': 'application/json',
        'API-Key': this.API_KEY
      },
      extraData: {
        images: [imageData],
        modifiers: ['common_names', 'scientific_names'],
        plant_details: ['common_names']

}

  );
  
  const result = JSON.parse(response.result);
  if (result.suggestions && result.suggestions.length > 0) {
    const suggestion = result.suggestions[0];
    return {
      id: suggestion.id,
      name: suggestion.plant_name,
      scientificName: suggestion.scientific_name,
      confidence: suggestion.probability,
      timestamp: Date.now(),
      imageUri: '' // 实际应用中应保存图片路径
    };

} catch (e) {

  console.error(Failed to identify plant via cloud API. Code: {e.code}, message: {e.message});

return null;

private async imageToBase64(imageObj: image.Image): Promise<string> {

// 简化的图像转换逻辑 (实际应用中应使用更完整的实现)
const buffer = await imageObj.getComponent(image.ImageComponent.JPEG);
return data:image/jpeg;base64,${buffer.toString('base64')};

public async getPlantDetail(plantId: string): Promise<PlantDetail | null> {

try {
  // 调用云端API获取植物详情
  const response = await this.httpRequest.request(
    ${this.API_URL}/details,

method: http.RequestMethod.GET,

      header: {
        'API-Key': this.API_KEY
      },
      extraData: {
        plant_id: plantId

}

  );
  
  const detail = JSON.parse(response.result);
  return {
    id: plantId,
    watering: detail.watering || '未知',
    sunlight: detail.sunlight || '未知',
    soil: detail.soil || '未知',
    temperature: detail.temperature || '未知',
    fertilization: detail.fertilization || '未知',
    pruning: detail.pruning || '未知'
  };

catch (e) {

  console.error(Failed to get plant details. Code: {e.code}, message: {e.message});
  return null;

}

private async addToHistory(plantInfo: PlantInfo): Promise<void> {
this.plantHistory.unshift(plantInfo);
if (this.plantHistory.length > 50) {
this.plantHistory = this.plantHistory.slice(0, 50);
await this.syncHistory();

private async syncHistory(): Promise<void> {

if (this.kvStore) {
  try {
    await this.kvStore.put('plant_history', { value: this.plantHistory });

catch (e) {

    console.error(Failed to sync plant history. Code: {e.code}, message: {e.message});

}

private notifyHistoryChange(newHistory: PlantInfo[]): void {

// 合并新旧历史记录,去重
const mergedHistory = [...this.plantHistory];

newHistory.forEach(newItem => {
  if (!mergedHistory.some(item => item.id === newItem.id)) {
    mergedHistory.push(newItem);

});

// 按时间戳排序
this.plantHistory = mergedHistory.sort((a, b) => b.timestamp - a.timestamp).slice(0, 50);

public async getPlantHistory(): Promise<PlantInfo[]> {

if (!this.kvStore) return this.plantHistory;

try {
  const entry = await this.kvStore.get('plant_history');
  return entry?.value || this.plantHistory;

catch (e) {

  console.error(Failed to get plant history. Code: {e.code}, message: {e.message});
  return this.plantHistory;

}

public async captureAndIdentify(): Promise<PlantInfo | null> {
if (!this.previewOutput) return null;

try {
  const imageObj = await this.previewOutput.getFrame();
  const plantInfo = await this.identifyPlant(imageObj);
  
  if (plantInfo) {
    await this.addToHistory(plantInfo);

imageObj.release();

  return plantInfo;

catch (e) {

  console.error(Failed to capture and identify plant. Code: {e.code}, message: {e.message});
  return null;

}

public async destroy(): Promise<void> {
if (this.kvStore) {
this.kvStore.off(‘dataChange’);
if (this.cameraInput) {

  await this.cameraInput.close();

if (this.imageClassifier) {

  this.imageClassifier.release();

}

植物识别组件

// src/main/ets/components/PlantIdentifier.ets
@Component
export struct PlantIdentifier {
private plantService = PlantService.getInstance();
@State identifiedPlant: PlantInfo | null = null;
@State plantDetail: PlantDetail | null = null;
@State previewSurfaceId: string = ‘previewSurface’;
@State showDetail: boolean = false;
@State history: PlantInfo[] = [];

aboutToAppear(): void {
this.loadHistory();
private async loadHistory(): Promise<void> {

this.history = await this.plantService.getPlantHistory();

build() {

Stack() {
  // 摄像头预览
  CameraPreview({ surfaceId: this.previewSurfaceId })
    .width('100%')
    .height('60%');
  
  // 识别按钮
  Button('识别植物')
    .type(ButtonType.Circle)
    .width(80)
    .height(80)
    .backgroundColor('#4CAF50')
    .fontColor('#FFFFFF')
    .position({ x: '50%', y: '80%' })
    .margin({ left: -40 })
    .onClick(() => {
      this.identifyPlant();
    });
  
  // 识别结果
  if (this.identifiedPlant) {
    this.buildResultCard()
      .position({ x: '10%', y: '10%' });

// 历史记录按钮

  Button($r('app.media.ic_history'))
    .type(ButtonType.Circle)
    .width(60)
    .height(60)
    .backgroundColor('#2196F3')
    .fontColor('#FFFFFF')
    .position({ x: '85%', y: '10%' })
    .onClick(() => {
      this.showHistory = true;
    });

.width(‘100%’)

.height('100%')

// 植物详情对话框
if (this.showDetail && this.plantDetail) {
  Dialog.show({
    title: this.identifiedPlant?.name || '植物详情',
    content: this.buildDetailContent(),
    confirm: {
      value: '关闭',
      action: () => {
        this.showDetail = false;

}

  });

// 历史记录对话框

if (this.showHistory) {
  Dialog.show({
    title: '识别历史',
    content: this.buildHistoryContent(),
    confirm: {
      value: '关闭',
      action: () => {
        this.showHistory = false;

}

  });

}

@Builder
private buildResultCard() {
Column() {
Text(this.identifiedPlant?.name || ‘未知植物’)
.fontSize(20)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 5 });

  Text(置信度: ${Math.floor((this.identifiedPlant?.confidence || 0) * 100)}%)
    .fontSize(14)
    .fontColor('#666666')
    .margin({ bottom: 10 });
  
  Button('查看养护方法')
    .type(ButtonType.Capsule)
    .width('80%')
    .backgroundColor('#FF4081')
    .fontColor('#FFFFFF')
    .margin({ bottom: 10 })
    .onClick(() => {
      this.showPlantDetail();
    });
  
  Button('添加到我的花园')
    .type(ButtonType.Capsule)
    .width('80%')
    .backgroundColor('#2196F3')
    .fontColor('#FFFFFF')
    .onClick(() => {
      this.addToMyGarden();
    });

.width(‘80%’)

.padding(15)
.backgroundColor('#FFFFFF')
.borderRadius(15)
.shadow({ radius: 10, color: '#E0E0E0', offsetX: 0, offsetY: 5 });

@Builder

private buildDetailContent() {
if (!this.plantDetail) return;

Scroll() {
  Column() {
    Row() {
      Text('浇水:')
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .margin({ right: 10 });
      
      Text(this.plantDetail.watering)
        .fontSize(16);

.margin({ bottom: 15 });

    Row() {
      Text('光照:')
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .margin({ right: 10 });
      
      Text(this.plantDetail.sunlight)
        .fontSize(16);

.margin({ bottom: 15 });

    Row() {
      Text('土壤:')
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .margin({ right: 10 });
      
      Text(this.plantDetail.soil)
        .fontSize(16);

.margin({ bottom: 15 });

    Row() {
      Text('温度:')
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .margin({ right: 10 });
      
      Text(this.plantDetail.temperature)
        .fontSize(16);

.margin({ bottom: 15 });

    Row() {
      Text('施肥:')
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .margin({ right: 10 });
      
      Text(this.plantDetail.fertilization)
        .fontSize(16);

.margin({ bottom: 15 });

    Row() {
      Text('修剪:')
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .margin({ right: 10 });
      
      Text(this.plantDetail.pruning)
        .fontSize(16);

}

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

.width(‘100%’)

.height(300)

@Builder

private buildHistoryContent() {
Column() {
if (this.history.length > 0) {
List({ space: 10 }) {
ForEach(this.history, (item) => {
ListItem() {
Row() {
Image($r(‘app.media.ic_plant’))
.width(40)
.height(40)
.margin({ right: 15 });

            Column() {
              Text(item.name)
                .fontSize(16)
                .fontWeight(FontWeight.Bold);
              
              Text(new Date(item.timestamp).toLocaleString())
                .fontSize(12)
                .fontColor('#666666');

.layoutWeight(1);

            Text(${Math.floor(item.confidence * 100)}%)
              .fontSize(14)
              .fontColor('#4CAF50');

.width(‘100%’)

          .padding(10)
          .onClick(() => {
            this.loadPlantFromHistory(item);
          });

})

.width(‘100%’)

    .height(400)

else {

    Text('暂无识别历史')
      .fontSize(16)
      .fontColor('#666666')
      .margin({ top: 50 });

}

.width('100%')
.height('100%')

private async identifyPlant(): Promise<void> {

const plantInfo = await this.plantService.captureAndIdentify();
this.identifiedPlant = plantInfo;
if (plantInfo) {
  this.plantDetail = await this.plantService.getPlantDetail(plantInfo.id);

}

private async showPlantDetail(): Promise<void> {
if (this.identifiedPlant && !this.plantDetail) {
this.plantDetail = await this.plantService.getPlantDetail(this.identifiedPlant.id);
this.showDetail = true;

private async addToMyGarden(): Promise<void> {

if (this.identifiedPlant) {
  // 实际应用中应该保存到用户的花园列表
  console.log(Added ${this.identifiedPlant.name} to my garden);

}

private async loadPlantFromHistory(item: PlantInfo): Promise<void> {
this.identifiedPlant = item;
this.plantDetail = await this.plantService.getPlantDetail(item.id);
this.showHistory = false;
}

主界面实现

// src/main/ets/pages/PlantPage.ets
import { PlantService } from ‘…/service/PlantService’;
import { PlantIdentifier } from ‘…/components/PlantIdentifier’;

@Entry
@Component
struct PlantPage {
@State activeTab: number = 0;
@State deviceList: string[] = [];
private plantService = PlantService.getInstance();

build() {
Column() {
// 标题
Text(‘智能植物识别’)
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 20 });

  // 标签页
  Tabs({ barPosition: BarPosition.Start }) {
    TabContent() {
      // 植物识别标签页
      PlantIdentifier()

.tabBar(‘识别植物’);

    TabContent() {
      // 我的花园标签页
      this.buildGardenTab()

.tabBar(‘我的花园’);

    TabContent() {
      // 设备管理标签页
      this.buildDevicesTab()

.tabBar(‘设备管理’);

.barWidth(‘100%’)

  .barHeight(50)
  .width('100%')
  .height('80%')

.width(‘100%’)

.height('100%')
.padding(20)
.onAppear(() => {
  // 模拟获取设备列表
  setTimeout(() => {
    this.deviceList = ['我的手机', '平板电脑', '智能手表'];
  }, 1000);
});

@Builder

private buildGardenTab() {
Column() {
Text(‘我的花园’)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 20 });

  // 模拟花园植物
  Grid() {
    GridItem() {
      this.buildPlantCard('玫瑰', 'Rosa rugosa');

GridItem() {

      this.buildPlantCard('向日葵', 'Helianthus annuus');

GridItem() {

      this.buildPlantCard('仙人掌', 'Cactaceae');

GridItem() {

      this.buildPlantCard('绿萝', 'Epipremnum aureum');

}

  .columnsTemplate('1fr 1fr')
  .rowsTemplate('1fr 1fr')
  .columnsGap(15)
  .rowsGap(15)
  .width('100%')
  .height('70%');
  
  Button('添加新植物')
    .type(ButtonType.Capsule)
    .width('80%')
    .margin({ top: 20 })
    .backgroundColor('#4CAF50')
    .fontColor('#FFFFFF');

.width(‘100%’)

.height('100%')
.padding(10);

@Builder

private buildPlantCard(name: string, scientificName: string) {
Column() {
Image($r(‘app.media.ic_plant’))
.width(80)
.height(80)
.margin({ bottom: 10 });

  Text(name)
    .fontSize(16)
    .fontWeight(FontWeight.Bold);
  
  Text(scientificName)
    .fontSize(12)
    .fontColor('#666666')
    .margin({ top: 5 });

.width(‘100%’)

.height('100%')
.padding(15)
.backgroundColor('#FFFFFF')
.borderRadius(10)
.shadow({ radius: 5, color: '#E0E0E0', offsetX: 0, offsetY: 2 })
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center);

@Builder

private buildDevicesTab() {
Column() {
Text(‘已连接设备’)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 20 });

  if (this.deviceList.length > 0) {
    List({ space: 15 }) {
      ForEach(this.deviceList, (device) => {
        ListItem() {
          Row() {
            Image($r('app.media.ic_device'))
              .width(40)
              .height(40)
              .margin({ right: 15 });
            
            Text(device)
              .fontSize(16)
              .layoutWeight(1);
            
            if (device === '我的手机') {
              Text('主设备')
                .fontSize(14)
                .fontColor('#4CAF50');

}

          .width('100%')
          .padding(15)
          .backgroundColor('#FFFFFF')
          .borderRadius(10)

})

.width(‘100%’)

    .layoutWeight(1);

else {

    Text('没有连接的设备')
      .fontSize(16)
      .fontColor('#666666')
      .margin({ top: 50 });

Button(‘添加设备’)

    .type(ButtonType.Capsule)
    .width('80%')
    .margin({ top: 30 })
    .backgroundColor('#2196F3')
    .fontColor('#FFFFFF');

.width(‘100%’)

.height('100%')
.padding(10);

}

四、与游戏同步技术的结合点
分布式数据同步:借鉴游戏中多玩家状态同步机制,实现植物识别历史的跨设备同步

实时图像处理:类似游戏中的实时数据流,处理摄像头图像数据

设备角色分配:类似游戏中的主机/客户端角色,确定主识别设备和从属设备

冲突解决策略:使用时间戳优先策略解决多设备同时添加植物的冲突

数据压缩传输:优化植物识别结果的传输效率,类似游戏中的网络优化

五、关键特性实现
混合识别模式:

  const localResult = await this.localIdentify(imageObj);

if (localResult.confidence < 0.7) {
const cloudResult = await this.cloudIdentify(imageObj);
if (cloudResult) return cloudResult;
return localResult;

植物详情获取:

  const response = await this.httpRequest.request(
 ${this.API_URL}/details,

method: http.RequestMethod.GET,

   header: { 'API-Key': this.API_KEY },
   extraData: { plant_id: plantId }

);

历史记录同步:

  this.kvStore.on('dataChange', (data) => {
 data.insertEntries.forEach((entry) => {
   if (entry.key === 'plant_history') {
     this.notifyHistoryChange(entry.value.value as PlantInfo[]);

});

});

图像处理优化:

  const buffer = await imageObj.getComponent(image.ImageComponent.JPEG);

return data:image/jpeg;base64,${buffer.toString(‘base64’)};

六、性能优化策略
本地缓存优先:

  public async getPlantHistory(): Promise<PlantInfo[]> {
 // 先返回本地缓存
 const cachedHistory = this.plantHistory;
 
 // 异步从分布式存储获取最新历史
 if (this.kvStore) {
   this.kvStore.get('plant_history').then((entry) => {
     if (entry?.value) {
       this.plantHistory = entry.value;

});

return cachedHistory;

批量历史更新:

  private async addToHistory(plantInfo: PlantInfo): Promise<void> {
 this.plantHistory.unshift(plantInfo);
 if (this.plantHistory.length > 50) {
   this.plantHistory = this.plantHistory.slice(0, 50);

await this.syncHistory();

资源释放管理:

  public async destroy(): Promise<void> {
 if (this.imageClassifier) {
   this.imageClassifier.release();

if (this.cameraInput) {

   await this.cameraInput.close();

}

网络请求节流:

  private lastApiRequestTime = 0;

private readonly API_REQUEST_INTERVAL = 3000; // 3秒请求间隔

private async cloudIdentify(imageObj: image.Image): Promise<PlantInfo | null> {
const now = Date.now();
if (now - this.lastApiRequestTime < this.API_REQUEST_INTERVAL) {
return null;
this.lastApiRequestTime = now;

 // API请求逻辑...

七、项目扩展方向
植物健康监测:通过图像分析植物健康状况

养护提醒:设置浇水、施肥等提醒

社区分享:分享植物照片和养护经验

AR植物展示:通过AR技术展示植物生长过程

植物百科:建立本地植物知识库

八、总结

本植物识别园艺助手实现了以下核心功能:
基于HarmonyOS的本地和云端混合植物识别

植物养护知识获取与展示

识别历史记录的分布式同步

直观的用户界面和交互体验

通过借鉴游戏中的多设备同步技术,我们构建了一个实用的园艺辅助工具。该项目展示了HarmonyOS在图像处理和分布式技术方面的强大能力,为开发者提供了生活服务类应用开发的参考方案。

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