본문 바로가기

과거공부모음

20230321 TIL - 구글 비전을 이용한 이미지 라벨링

Today I learned

비슷한 사진을 추천해주는 기능을 작업하고 있다

구글 비전을 이용해서 사진에 라벨링을 하고 그 라벨을 이용해서 사진을 추천해주는 기능이다

구글 비전 API를 이용해서 생각 보다 쉽게 사진을 분석하고 라벨링을 뽑아올 수 있었다

async createPhotospot(
    createPhtospotDto: CreatePhotospotDto,
    files: Express.Multer.File[],
    userId: number,
    collectionId: number
  ): Promise<void> {
    const collection = await this.collectionRepository.findOne({ where: { id: collectionId } });

    if (_.isNil(collection)) {
      throw new NotFoundException('해당 콜렉션을 찾을 수 없습니다.');
    }

    const queryRunner = this.dataSource.createQueryRunner();
    await queryRunner.connect();
    await queryRunner.startTransaction();

    try {
      const { title, description, latitude, longitude }: CreatePhotospotDto = createPhtospotDto;
      const photospot = await queryRunner.manager
        .getRepository(Photospot)
        .insert({ title, description, latitude, longitude, userId, collectionId });
      for (const file of files) {
        try {
          const image = await this.s3Service.putObject(file);
          const photo = await queryRunner.manager
            .getRepository(Photo)
            .insert({ image, userId, photospotId: photospot.identifiers[0].id });
          const photoKeywords = await this.googleVisionService.image(image);
			    for (const photoKeyword of photoKeywords) {
			      if (_.isUndefined(photoKeyword)) {
			        continue;
			      }

					  const photo = await this.photoRepository.findOne({where: {id: photoId}});
			      const preKeyword = await this.photoKeywordRepository.findOne({ where: { keyword: photoKeyword } });
			      if (_.isNil(preKeyword)) {
			        const insertKeyword = await this.photoKeywordRepository.save({ keyword: photoKeyword });
			        await this.dataSource.query(`INSERT INTO photo_photo_keywords_photo_keyword VALUES (${photo.id}, ${insertKeyword.id})`);
			      } else {
			        await this.dataSource.query(`INSERT INTO photo_photo_keywords_photo_keyword VALUES (${photo.id}, ${preKeyword.id})`);
			      }
					}
        } catch (error) {
          console.log(error);
          throw new Error('Photo 입력 실패.');
        }
      }
      await queryRunner.commitTransaction();
    } catch (error) {
      console.log(error);
      await queryRunner.rollbackTransaction();
      throw new BadRequestException('요청이 올바르지 않습니다.');
    } finally {
      await queryRunner.release();
    }
  }

해당 코드 처럼 구현을 했다 하지만 여기서 문제가 발생했다

 

사진 업로드의 시간이 너무 오래걸린다는 것이다 로컬에서 혼자 테스트를 하는 경우에도 시간이 오래 걸리는데 여러 사용자가 사용한다고 한다면 생각만해도 사용할 수 없는 기능이라고 생각한다

이 문제를 해결해야한다

 

문제는 구글 비전을 이용해서 라벨을 뽑아오는 시간이 너무 오래 걸리는 부분이다

생각을 해봤다 과연 await를 걸어서 기다릴 만큼 포토스팟과 포토를 저장할 때 중요한가?

 

나는 아니라고 생각했다 포토와 포토스팟은 같이 저장되고 사용자에게 보여줘야 하지만 라벨의 경우 나중에 붙이고 사진 추천기능이 필요할 때 사용할 수 있기만 하면 되는 부분이다

 

그래서 사진 라벨을 뽑아오고 저장하는 부분은 동기적으로 동작할 필요가 없고 비동기적으로 작동시크는 방법으로 코드를 수정했다

async createImageKeyword(image: string, photoId: number): Promise<void> {
    const photoKeywords = await this.googleVisionService.image(image);
    const keyword = []
    for (const photoKeyword of photoKeywords) {
      if (_.isUndefined(photoKeyword)) {
        continue;
      }
		  const photo = await this.photoRepository.findOne({where: {id: photoId}});
      const preKeyword = await this.photoKeywordRepository.findOne({ where: { keyword: photoKeyword } });
      if (_.isNil(preKeyword)) {
        const insertKeyword = await this.photoKeywordRepository.save({ keyword: photoKeyword });
        await this.dataSource.query(`INSERT INTO photo_photo_keywords_photo_keyword VALUES (${photo.id}, ${insertKeyword.id})`);
      } else {
        await this.dataSource.query(`INSERT INTO photo_photo_keywords_photo_keyword VALUES (${photo.id}, ${preKeyword.id})`);
      }
    }
  }
}

키워드만 처리해주는 메서드를 만들고 비동기로 작동시키는 방식으로 수정했다

 

이 코드에서 사용자가 사용하면서 포토스팟이 저장되는게 너무 느리다라고 느끼는 성능적인 부분을 개선하면서 문제를 해결했다

 

이제는 두 번째 문제다 키워드 만큼 select, insert를 너무 자주한다는 것이다 insert만큼은 줄일 수 있지 않을까?

 

문제를 해결해보도록 하자

async createImageKeyword(image: string, photoId: number): Promise<void> {
    const photoKeywords = await this.googleVisionService.image(image);
    const keyword = []
    for (const photoKeyword of photoKeywords) {
      if (_.isUndefined(photoKeyword)) {
        continue;
      }
  
      const preKeyword = await this.photoKeywordRepository.findOne({ where: { keyword: photoKeyword } });
      if (_.isNil(preKeyword)) {
        const insertKeyword = await this.photoKeywordRepository.save({ keyword: photoKeyword });
        keyword.push(insertKeyword);
      } else {
        keyword.push(preKeyword);
      }
    }
    const photo = await this.photoRepository.findOne({where: {id: photoId}});
    if (_.isNil(photo)) {
      return;
    }
    photo.photoKeywords = keyword;
    await this.photoRepository.save(photo);
  }

키워드 객체를 배열에다 저장을 하고 save를 이용해서 insert를 한 번 진행하는 방식으로 코드를 수정했다

 

typeorm의 옵션을 logging을 확인 할 수 있게 하고 수정 전 코드와 수정 후 코드를 비교해보면 확실하게 쿼리의 양이 줄어든 것을 확인할 수 있었다