개발

Image Resize and Padding

내공얌냠 2022. 12. 17. 12:34

이미지와 바운딩박스를 리사이즈, 패딩해주는 소스

 

* 640 x 480 -> 480 x 480 으로 변환하는 것 같이 height가 같은 경우는 처리했지만, 필요 시 width가 같은 경우 추가 처리가 필요하다.

함수 설명

SetImagePadding

이미지 패딩처리

  • image_path: 원본 이미지 경로
  • save_image_path: 저장할 이미지 경로
  • finalTargetSize: 패딩 처리한 이후의 이미지 사이즈

SetImageResizeNPadding

이미지 리사이즈 및 패딩 처리

  • image_path : 원본 이미지 경로
  • save_image_path : 저장할 이미지 경로
  • targetWidth : 리사이즈할 이미지 사이즈
  • targetHeight : 리사이즈할 이미지 사이즈
  • finalTargetSize : 패딩 처리 이후 최종 이미지 사이즈

SetBboxPadding

바운딩 박스 패딩처리

  • xmin : 바운딩 박스 좌표 xmin
  • ymin : 바운딩 박스 좌표 ymin
  • xmax : 바운딩 박스 좌표 xmax
  • ymax : 바운딩 박스 좌표 ymax
  • targetWidth : 패딩 전 이미지 크기
  • targetHeight : 패딩 전 이미지 크기
  • finalTargetSize : 패딩 후 최종 이미지 크기

SetBboxResizeNPadding

바운딩 박스 리사이즈 및 패딩 처리

  • imageToPredict: 원본 이미지
  • xmin : 바운딩 박스 좌표 xmin
  • ymin : 바운딩 박스 좌표 ymin
  • xmax : 바운딩 박스 좌표 xmax
  • ymax : 바운딩 박스 좌표 ymax
  • targetWidth : 리사이즈 후이면서 패딩 전 이미지 크기
  • targetHeight : 리사이즈 후이면서 패딩 전 이미지 크기
  • finalTargetSize : 패딩 후 최종 이미지 크기

SaveResizeNPadding(xml_path, current_img_path, new_image_path, new_txt_path, targetWidth, targetHeight, finalTargetSize, label2idx)

이미지와 바운딩 박스를 리사이즈/패딩 처리

  • xml_path : 원본 xml 경로
  • current_img_path : 원본 이미지 경로
  • new_image_path : 이미지를 리사이즈/패딩 처리하여 변경된 이미지를 저장할 경로
  • new_txt_path : xml 내 값을 리사이즈/패딩 처리하여 변경된 내용을 .txt로 저장할 경로
  • targetWidth : 리사이즈 후이면서 패딩 전 이미지 크기
  • targetHeight : 리사이즈 후이면서 패딩 전 이미지 크기
  • finalTargetSize : 패딩 후 최종 이미지 크기
  • label2idx: 라벨명을 숫자 라벨로 바꿔주기 위한 dictionary

소스

import os
import glob
import pandas as pd
import xml.etree.ElementTree as ET
from tqdm.notebook import tqdm
from google.colab.patches import cv2_imshow
import cv2
import numpy as np

def SetImagePadding(image_path, save_image_path, finalTargetSize):
    # 이미지 받을 경로, 저장할 경로, 중간 너비, 중간 높이, 최종 크기(정사각형이므로 길이하나만)
    # load image
    imageToPredict = cv2.imread(image_path, 3) # 단일 이미지 읽기
    img = np.array(imageToPredict); # 이미지 행렬화

    # padding
    y_zero, x_zero, target_height, target_width = (0, 0, img.shape[0], img.shape[1]) # 바뀐 이미지의 패딩계산을 위한 크기

    # 그림 주변에 검은색으로 칠하기
    padding_width = (finalTargetSize - (target_width - x_zero))/2  # w_x = (targetWidth - 그림)을 뺀 나머지 영역 크기 [ 그림나머지/2 [그림] 그림나머지/2 ]
    padding_height = (finalTargetSize - (target_height - y_zero))/2

    if(padding_width < 0):         # 크기가 -면 0으로 지정.
        padding_width = 0
    elif(padding_height < 0):
        padding_height = 0

    M = np.float32([[1, 0, padding_width], [0, 1, padding_height]])  #(2*3 이차원 행렬)
    img_re = cv2.warpAffine(img, M, (finalTargetSize, finalTargetSize)) # 패딩 실행

    # save image
    cv2.imwrite(save_image_path, img_re) # 저장


def SetImageResizeNPadding(image_path, save_image_path, targetWidth, targetHeight, finalTargetSize):
    # 이미지 받을 경로, 저장할 경로, 중간 너비, 중간 높이, 최종 크기(정사각형이므로 길이하나만)
    # load image
    imageToPredict = cv2.imread(image_path, 3) # 단일 이미지 읽기
    img = np.array(imageToPredict); # 이미지 행렬화

    # resize
    img = cv2.resize(imageToPredict, (targetWidth, targetHeight)); # 이미지 리사이즈
    img = np.array(img); # resize 된 이미지 행렬화

    # padding
    y_zero, x_zero, target_height, target_width = (0, 0, img.shape[0], img.shape[1]) # 바뀐 이미지의 패딩계산을 위한 크기

    # 그림 주변에 검은색으로 칠하기
    padding_width = (finalTargetSize - (target_width - x_zero))/2  # w_x = (targetWidth - 그림)을 뺀 나머지 영역 크기 [ 그림나머지/2 [그림] 그림나머지/2 ]
    padding_height = (finalTargetSize - (target_height - y_zero))/2

    if(padding_width < 0):         # 크기가 -면 0으로 지정.
        padding_width = 0
    elif(padding_height < 0):
        padding_height = 0

    M = np.float32([[1, 0, padding_width], [0, 1, padding_height]])  #(2*3 이차원 행렬)
    img_re = cv2.warpAffine(img, M, (finalTargetSize, finalTargetSize)) # 패딩 실행

    # save image
    cv2.imwrite(save_image_path, img_re) # 저장

    # return img, img_re

def SetBboxPadding(xmin, ymin, xmax, ymax, targetWidth, targetHeight, finalTargetSize):
    
    # padding
    y_zero, x_zero, target_height, target_width = (0, 0, targetHeight, targetWidth) # 원점설정과 패딩 추가 전 이미지 크기

    # 그림 주변에 검은색으로 칠하기
    padding_width = (finalTargetSize - (target_width - x_zero))/2  # padding_width = (targetWidth - 그림)을 뺀 나머지 영역 크기 [ 그림나머지/2 [그림] 그림나머지/2 ]
    padding_height = (finalTargetSize - (target_height - y_zero))/2  # 패딩크기

    if(padding_width < 0):         # 크기가 -면 0으로 지정.
        padding_width = 0
    elif(padding_height < 0):
        padding_height = 0

    return (int(xmin + padding_width), int(ymin + padding_height), int(xmax + padding_width), int(ymax + padding_height))


def SetBboxResizeNPadding(imageToPredict, xmin, ymin, xmax, ymax, targetWidth, targetHeight, finalTargetSize):
    # imageToPredict <- img # 패딩 없이 받아와서 패딩 추가 후 bbox 정렬
    img = np.array(imageToPredict); # 행렬화

    original_img_height = imageToPredict.shape[0] # 이미지의 높이
    original_img_width = imageToPredict.shape[1] # 너비
    x_scale = targetWidth / original_img_width # 패딩을 제외한 크기로 줄이기 위한 x비율
    y_scale = targetHeight / original_img_height # 패딩을 제외한 크기로 줄이기 위한 y비율
    # print(original_img_height, original_img_width, targetWidth, targetHeight)
    # print(xmin, ymin, xmax, ymax)
    xmin = int(np.round(xmin * x_scale)) # 비율에 맞추어 위치 재정렬 / 소수점 반올림 
    ymin = int(np.round(ymin * y_scale))  
    xmax = int(np.round(xmax * x_scale))
    ymax = int(np.round(ymax * y_scale))
    # print(xmin, ymin, xmax, ymax)
    # padding
    y_zero, x_zero, target_height, target_width = (0, 0, targetHeight, targetWidth) # 원점설정과 패딩 추가 전 이미지 크기

    # 그림 주변에 검은색으로 칠하기
    padding_width = (finalTargetSize - (target_width - x_zero))/2  # padding_width = (targetWidth - 그림)을 뺀 나머지 영역 크기 [ 그림나머지/2 [그림] 그림나머지/2 ]
    padding_height = (finalTargetSize - (target_height - y_zero))/2  # 패딩크기
    # print(padding_width, padding_height)
    if(padding_width < 0):         # 크기가 -면 0으로 지정.
        padding_width = 0
    elif(padding_height < 0):
        padding_height = 0

    return (int(xmin + padding_width), int(ymin + padding_height), int(xmax + padding_width), int(ymax + padding_height))


# xml 폴더경로, img 폴더경로, 새로운 img 저장할 경로, 새로운 txt 저장할 경로
def SaveResizeNPadding(xml_path, current_img_path, new_image_path, new_txt_path, targetWidth, targetHeight, finalTargetSize, label2idx):
    # load xml
    for xml_file in tqdm(glob.glob(xml_path + '/*.xml')): # 경로상 xml 모두 경로 포함 불러오기

        img_file_name = xml_file.split('/')[-1][:-4]                      # 파일명
        ouput_txt_filename = f'{new_txt_path}' + img_file_name + '.txt'   # 원하는 경로 / f'{dest_path}' +
        current_img_filename = current_img_path + img_file_name  + '.jpg' # 현재 xml 파일과 짝인 image 파일
        new_img_filename = new_image_path + img_file_name + '_{0}_{0}.jpg'.format(finalTargetSize, finalTargetSize) # 만들어줄 image 파일
        
        tree = ET.parse(xml_file) # xml을 읽기 
        root = tree.getroot()     # 변환

        original_image_width = int(root.find('size').find('width').text)      # 원래 이미지 크기
        original_image_height = int(root.find('size').find('height').text)
        # 이미지 크기가 바꾸고자 하는 이미지와 같을 경우 리사이즈를 안하고 패딩 처리만 하기 위해
        is_original_size_same = targetWidth == original_image_width or targetHeight == original_image_height   
        # 이미지 크기가 가로-세로, 세로-가로가 하나라도 바꾸고자 하는 이미지와 같을 경우 같은 구간은 맞춰주기 위해 
        is_original_size_small = targetWidth == original_image_height or targetHeight == original_image_width

        # image resize and padding
        if is_original_size_same:
          img_only_padding = SetImagePadding(current_img_filename, new_img_filename, finalTargetSize)
        elif is_original_size_small:
          # 최종 이미지와 원본 이미지와의 scale 계산
          original_image_scale = finalTargetSize/original_image_width           
          # 현재는 640 x 480 -> 480 x 480 같이 height가 같을 경우만 처리함. 재사용시 width가 같은 경우도 처리 필요
          targetHeight = int(original_image_height * original_image_scale)    
          SetImageResizeNPadding(current_img_filename, new_img_filename, targetWidth, targetHeight, finalTargetSize)
        else:
          SetImageResizeNPadding(current_img_filename, new_img_filename, targetWidth, targetHeight, finalTargetSize)

        original_img = cv2.imread(current_img_filename)
        f = open(ouput_txt_filename, 'w') # 작성 시작

        # bbox resize and padding
        # get xmin, ymin, xmax, ymax, width, height
        for member in root.findall('object'):           # 변환된 xml에서 객체수만큼 반복
            bbx = member.find('bndbox')                 # bbox 태그
            label = member.find('name').text            # trash 종류
            xmin = float(bbx.find('xmin').text)         # bbox 초기 xmin
            ymin = float(bbx.find('ymin').text)         # bbox 초기 ymin
            xmax = float(bbx.find('xmax').text)         # bbox 초기 xmax
            ymax = float(bbx.find('ymax').text)         # bbox 초기 ymax

            # 패딩 후 bbox 값을 튜플로 각 변수에 할당
            if is_original_size_same:
                (bbox_xmin, bbox_ymin, bbox_xmax, bbox_ymax) = SetBboxPadding(xmin, ymin, xmax, ymax, original_image_width, original_image_height, finalTargetSize)
            else:
                (bbox_xmin, bbox_ymin, bbox_xmax, bbox_ymax) = SetBboxResizeNPadding(original_img, xmin, ymin, xmax, ymax, 
                                                                              targetWidth, targetHeight, finalTargetSize)
            
            label_idx = label2idx[label]                    # trash의 라벨을 붙여준다
            x, y = bbox_xmin / finalTargetSize, bbox_ymin / finalTargetSize # 최종 이미지 크기에서의 비율로 변환
            w, h = (bbox_xmax - bbox_xmin) / finalTargetSize, (bbox_ymax - bbox_ymin) / finalTargetSize        

            info = f'{label_idx} {x} {y} {w} {h}' # 라벨과 나머지 값들을 통합
            
            f.write(info+'\n') # 파일 쓰기

실행

label2idx = {'bundle of ropes' : 0,'bundle of rope' : 0, 'rope' : 1, 'circular fish trap' : 2,'eel fish trap' : 3,'fish net' : 4,
                 'rectangular fish trap' : 5,'spring fish trap' : 6, 'tire' : 7, 'wood' : 8, 'other objects' : 9, 'other objets':9,
             'othe objects':9}


SaveResizeNPadding('/content/drive/MyDrive/Alpaco Object Detection/final/sonar_label/'
                  , '/content/drive/MyDrive/Alpaco Object Detection/final/sonar_image/'
                  , '/content/drive/MyDrive/Alpaco Object Detection/final/sonar_image_480/'
                  , '/content/drive/MyDrive/Alpaco Object Detection/final/sonar_label_480/'
                  , 480, 480, 480, label2idx)
                  
SaveResizeNPadding('/content/drive/MyDrive/Colab_Notebooks/Object_Detection_Project/final/underwater_label', 
                   '/content/drive/MyDrive/Colab_Notebooks/Object_Detection_Project/final/underwater_image/', 
                   '/content/img/', '/content/txt/', 640, 480, 640, label2idx)

참고

XML 파일 구조 및 내용

<?xml version="1.0" encoding="utf-8"?>
<annotation>
  <folder>[sonar]</folder>
  <filename>rope_spring fish trap_20111026_003_20027_12.jpg</filename>
  <path>../../[image]/[sonar]/rope_spring fish trap_20111026_003_20027_12.jpg</path>
  <size>
    <width>640</width>
    <height>640</height>
    <depth>3</depth>
  </size>
  <commoninfo>
    <datasetname>sonar dataset</datasetname>
    <createdate>2021-04-22 09:48:39.0</createdate>
  </commoninfo>
  <metainfo>
    <device>SonarBeam S-150</device>
    <viewername>PostScan</viewername>
    <viewerversion>v7.39</viewerversion>
    <location>
      <name>the West sea</name>
      <latitude>
        <DMS/>
        <DMM/>
        <DD>35.979781</DD>
      </latitude>
      <longitude>
        <DMS/>
        <DMM/>
        <DD>126.583333</DD>
      </longitude>
    </location>
    <depth-of-water>0.0</depth-of-water>
    <temperature/>
    <NTU/>
  </metainfo>
  <object>
    <name>rope</name>
    <bndbox>
      <xmin>276.57142857142856</xmin>
      <ymin>399.7142857142857</ymin>
      <xmax>634.2857142857142</xmax>
      <ymax>640.0</ymax>
      <width>357.71428571428567</width>
      <height>240.28571428571428</height>
    </bndbox>
    <grade>A</grade>
  </object>
  <object>
    <name>spring fish trap</name>
    <bndbox>
      <xmin>532.6051805492795</xmin>
      <ymin>318.0456496385726</ymin>
      <xmax>576.4202215874342</xmax>
      <ymax>336.0663520010395</ymax>
      <width>43.81504103815473</width>
      <height>18.020702362466864</height>
    </bndbox>
    <grade>A</grade>
  </object>
</annotation>

 

도움이 되셨길 바랍니다.

728x90
반응형