0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Python】OpenCv(またはPillow)を使ってExcelへの画像の貼り付けを自動化

Last updated at Posted at 2022-03-29

はじめに

今回はChromeの拡張サービスである「Colaboratory」と「Google Drive」を使用してPythonのコードを記述していきます。
Google Driveから「新規 → その他 → アプリを追加」からインストールしてください。(Chromeで検索してもインストールできます)

今回業務の中でレポート作成時のExcelへの画像の貼付けを自動化したので、簡単なサンプルを作り実装メモとして残そうと思います。
業務の自動化に対応して作成したものになりますので、改善のポイントは多々あると思いますが、必要な処理だけ切り取っていただき少しでも参考になればと思います。

※ 2022/03/30修正
「Pillow」を使ったほうがリサイズ後の画質が安定しましたので、Pilowで書き直し、OpenCvの処理はコメントアウトしました。

何をするか

スクリーンショット 2022-03-29 10.45.10.png

このような形でカテゴリーごとに横3枚を上限に画像を貼り付けるプログラムを書いていきます。
以下のようなディレクトリ構成になっています。

  • img
    • dog
         - dog1.jpeg
         - dog2.jpeg
         - dog3.jpeg
    • cat
         - cat1.jpeg
         - cat2.jpeg
         - cat3.jpeg
         - cat4.jpeg
    • panda
         - panda1.jpeg
         - panda2.jpeg
  • resize

全体コード

import openpyxl
from openpyxl.styles import Font
import datetime
import pytz
from pathlib import Path
# import cv2
from PIL import Image
from google.colab import drive

# TimeZone
tz = pytz.timezone('Japan')
start = tz.localize(datetime.datetime.now())
print('処理開始')
print(start.strftime('%Y/%m/%d %H:%M:%S'))

drive.mount('/content/drive')

# ファイル名
OUTPUT_FILE_NAME = f'/content/drive/My Drive/sample/image_output/file/animal_image.xlsx'
# 画像ディレクトリ
IMG_DIR = '/content/drive/My Drive/sample/image_output/file/img/'
RESIZE_IMG_DIR = '/content/drive/My Drive/sample/image_output/file/resize/'
# シート名
SHEET_TITLE = 'animal'
# リサイズ後の横幅
RESIZE_WIDTH = 300


# ディレクトリ内の画像ファイル名の一覧を取得
def get_images(dir_name):
  dir_path = Path(f'{IMG_DIR}{dir_name}/')
  # 画像ファイル名の一覧を取得
  img_file_names = []
  for f in dir_path.iterdir():
    img_file_names.append(f.name)
  # 昇順に並び替え
  img_file_names.sort
  return img_file_names

# 画像サイズをリサイズ
def resize_img(dir_name, img_name):
  original_img = Image.open( f'{IMG_DIR}{dir_name}/{img_name}')
  resize_img = original_img.resize((RESIZE_WIDTH, int(RESIZE_WIDTH * original_img.size[1] / original_img.size[0])))
  resize_height = resize_img.size[1]
  # ドライブ上にディレクトリを作成
  dir_path = Path(f'{RESIZE_IMG_DIR}{dir_name}')
  dir_path.mkdir(exist_ok = True)
  resize_img.save(f'{RESIZE_IMG_DIR}{img_name}')

  #↓OpenCv
  # # 画像ファイルパス
  # img_path = f'{IMG_DIR}{dir_name}/{img_name}'
  # # 画像のサイズを取得してリサイズ
  # original_img = cv2.imread(img_path)
  # height, width = original_img.shape[:2]
  # resize_height = round(height * (RESIZE_WIDTH / width))
  # resize_img = cv2.resize(original_img, dsize=(RESIZE_WIDTH, resize_height))
  # # ドライブ上にディレクトリを作成
  # dir_path = Path(f'{RESIZE_IMG_DIR}{dir_name}')
  # dir_path.mkdir(exist_ok = True)
  # # リサイズした画像を保存
  # cv2.imwrite(f'{RESIZE_IMG_DIR}{dir_name}/{img_name}', resize_img)

  return resize_height


# ブック作成
workbook = openpyxl.Workbook()
# 先頭シート取得
worksheet = workbook.worksheets[0]
# シート名を設定
worksheet.title = SHEET_TITLE
# 動物名のフォント
bold = Font(bold=True)

# Pathオブジェクトを作成
path = Path(IMG_DIR)
# ディレクトリの一覧を取得
dir_list = []
for f in path.iterdir():
  dir_list.append(f.name)

# 行を定義
row = 1
# 行の高さの最大値
max_height = 0

# 書き出し処理
for dir_name in dir_list:
  # 動物名を出力
  worksheet.cell(row, 1, dir_name).font = bold
  row += 1
  # ディレクトリ内の画像ファイル名の一覧を取得
  img_file_names = get_images(dir_name)
  # 列を定義
  column = 1
  # ファイルの横並び数をカウント
  count = 0
  # 画像ファイルを出力
  file_name_row = row
  row += 1
  for i, img_name in enumerate(img_file_names):
    count += 1
    if i != 0 and count <= 3:
      # 3枚めまでは横に配置
      column += 2
    elif count > 3:
      # 3枚以上ある場合改行
      column = 1
      row += 3
      file_name_row += 3
      max_height = 0
    # ファイル名を出力
    worksheet.cell(file_name_row, column, img_name)
    # 画像をリサイズして、リサイズ後の高さを取得
    height = resize_img(dir_name, img_name)
    if max_height < height:
      max_height = height  
    # Imageオブジェクトの生成
    img = openpyxl.drawing.image.Image(f'{RESIZE_IMG_DIR}{dir_name}/{img_name}')
    # セルの行列番号から、そのセルの番地を取得 
    cell_address = worksheet.cell(row, column).coordinate
    img.anchor = cell_address
    
    # ワークシートの行の高さを修正
    worksheet.row_dimensions[row].height = max_height * 0.75
    # セルの行列番号から、そのセルの列番号の文字列を取得
    column_char = openpyxl.utils.cell.coordinate_from_string(cell_address)[0]
    # ワークシートの列の幅を修正
    worksheet.column_dimensions[column_char].width = RESIZE_WIDTH * 0.13
    # シートに画像貼り付け
    worksheet.add_image(img)

  # 次の動物の行を指定
  row += 3


# 保存
workbook.save(OUTPUT_FILE_NAME)

end = tz.localize(datetime.datetime.now())
print('処理完了')
print(end.strftime('%Y/%m/%d %H:%M:%S'))
print(end - start)

PythonでのExcel抽出の記事は他にも書いてますので、今回は以下で今回のポイントについて簡単に説明していきます。

ライブラリのインポート

import openpyxl
from pathlib import Path
import cv2
from google.colab import drive

まずは必要なライブラリをインストールします。
今回画像処理で必要になってくるのは
import cv2
from pathlib import Pathこの2つになります。
import openpyxlこちらでexcelの処理を行います。

Google Drive, Colaboratoryを使用する場合、ドライブのマウントが必要なのでgoogle.clabからdriveもインポートしておきます。

ドライブのマウント

drive.mount('/content/drive')

Google Drive, Colaboratoryを使用する場合、ドライブのマウントを行います。

ディレクトリ一覧の取得

# Pathオブジェクトを作成
path = Path(IMG_DIR)
# ディレクトリの一覧を取得
dir_list = []
for f in path.iterdir():
  dir_list.append(f.name)

定数で定義した画像ディレクトリのパスからPathオブジェクトを生成し、
.iterdir()
で画像ディレクトリ配下の各ディレクトリ名を取得します。

書き出し処理

for dir_name in dir_list:
  ~
  ~
  ~

取得したディレクトリ名をループし各ディレクトリごとに画像を処理していきます。

ディレクトリ内の画像一覧のファイル名を取得

img_file_names = get_images(dir_name)

上で定義した関数を呼び出してディレクトリ内の画像ファイル名を取得します。

def get_images(dir_name):
  dir_path = Path(f'{IMG_DIR}{dir_name}/')
  # 画像ファイル名の一覧を取得
  img_file_names = []
  for f in dir_path.iterdir():
    img_file_names.append(f.name)
  # 昇順に並び替え
  img_file_names.sort
  return img_file_names

引数で渡されたディレクトリのPathオブジェクトを作成し、
同様に、.iterdir()で配下の画像ファイル一覧を取得し、
.nameでファイル名を取得します。

画像のリサイズ処理

for i, img_name in enumerate(img_file_names):
  ~
  ~
  height = resize_img(dir_name, img_name)

取得した画像ファイル名リストをループし、関数を呼び出しリサイズします。

def resize_img(dir_name, img_name):
  # 画像ファイルパス
  img_path = f'{IMG_DIR}{dir_name}/{img_name}'
  # 画像のサイズを取得してリサイズ
  original_img = cv2.imread(img_path)
  height, width = original_img.shape[:2]
  resize_height = round(height * (RESIZE_WIDTH / width))
  resize_img = cv2.resize(original_img, dsize=(RESIZE_WIDTH, resize_height))
  # ドライブ上にディレクトリを作成
  dir_path = Path(f'{RESIZE_IMG_DIR}{dir_name}')
  dir_path.mkdir(exist_ok = True)
  # リサイズした画像を保存
  cv2.imwrite(f'{RESIZE_IMG_DIR}{dir_name}/{img_name}', resize_img)
  return resize_height

引数のディレクトリ名、ファイル名からcv2.imread(img_path)で元画像を読み込みます。
original_img.shape[:2]で画像の高さと幅を取得し、
今回は横幅300で統一してリサイズするので
round(height * (RESIZE_WIDTH / width))
でアスペクト比が崩れないようにリサイズ後の高さを取得します。

dir_path = Path(f'{RESIZE_IMG_DIR}{dir_name}')
dir_path.mkdir(exist_ok = True)
リサイズ後の画像を保存するresizeディレクトリに、元画像のディレクトリと同じようにディレクトリを作成します。
cv2.resize(original_img, dsize=(RESIZE_WIDTH, resize_height))でリサイズ後の画像を作成し、
cv2.imwrite(f'{RESIZE_IMG_DIR}{dir_name}/{img_name}', resize_img)で画像を保存します。

書き出し処理内で必要になるのでリサイズ後の高さをreturnします。

height = resize_img(dir_name, img_name)
    if max_height < height:
      max_height = height 

リサイズ後、同じ行の画像の高さの最大値を設定しておきます。

Pillowを使う場合(追記)

from PIL import Image
~
~
def resize_img(dir_name, img_name):
  original_img = Image.open( f'{IMG_DIR}{dir_name}/{img_name}')
  resize_img = original_img.resize((RESIZE_WIDTH, int(RESIZE_WIDTH * original_img.size[1] / original_img.size[0])))
  resize_height = resize_img.size[1]
  # ドライブ上にディレクトリを作成
  dir_path = Path(f'{RESIZE_IMG_DIR}{dir_name}')
  dir_path.mkdir(exist_ok = True)
  resize_img.save(f'{RESIZE_IMG_DIR}{img_name}')

  return resize_height

「Pillow」を使う場合、ライブラリをインストールし、リサイズの関数を上記のように書いてださい。
処理内容はOpenCvとほぼ変わりません。
私はPillowのほうがリサイズ後の画質が良かったのでこちらを推奨します。

画像の貼り付け処理

    # Imageオブジェクトの生成
    img = openpyxl.drawing.image.Image(f'{RESIZE_IMG_DIR}{dir_name}/{img_name}')
    # セルの行列番号から、そのセルの番地を取得 
    cell_address = worksheet.cell(row, column).coordinate
    img.anchor = cell_address
    
    # ワークシートの行の高さを修正
    worksheet.row_dimensions[row].height = max_height * 0.75
    # セルの行列番号から、そのセルの列番号の文字列を取得
    column_char = openpyxl.utils.cell.coordinate_from_string(cell_address)[0]
    # ワークシートの列の幅を修正
    worksheet.column_dimensions[column_char].width = RESIZE_WIDTH * 0.13
    # シートに画像貼り付け
    worksheet.add_image(img)

openpyxl.drawing.image.Image(f'{RESIZE_IMG_DIR}{dir_name}/{img_name}')
リサイズ後の画像でImageオブジェクトを生成します。
worksheet.cell(row, column).coordinateで貼り付けるセルの番地を取得し、
img.anchor =Imageオブジェクトに設定します。

worksheet.row_dimensions[row].height = max_height * 0.75
`worksheet.column_dimensions[column_char].width = RESIZE_WIDTH * 0.13
高さの最大値で行の高さを修正し、列幅を300で修正します。

列の文字列に関して、
worksheet.cell(row, column).columnで取得できるという記事も多かったのですが、
私は上記コードで列番号(int)しか取得できなかったため、
openpyxl.utils.cell.coordinate_from_string(cell_address)[0]
こちらでセルの番地から取得しました。

worksheet.add_image(img)
こちらでワークシートに画像を貼り付けます。

あとはworksheet.save(OUTPUT_FILE_NAME)でファイルを保存して完了です。

参考

Pillow

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?