【Python】「正方形さん」を作ってみた

# デスクトップ上で「正方形さん」を使いたい
「一眼レフで撮影した縦画像をInstagramに投稿する際、上下がトリミングされてしまう…」
「写真に余白をつけてすっきり見せたい…」
そんな時に重宝していたのが「正方形さん」というアプリ。

![スクリーンショット 2022-05-21 205133.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2649917/2ecc54ca-43ec-23c7-ee18-3e4a388318f6.png)

このアプリは画像が正方形になるように白埋めしてくれます。

しかし、わざわざスマホ上で一枚一枚変換するのも面倒なので、PC上で一括でできると良いな～と思っていました。

「[Ralpha](https://forest.watch.impress.co.jp/library/software/ralpha/)」などのデスクトップ向けフリーソフトもあるようですが、機能としては「写真に余白をつける」程度のものなので、さっと自分で作ってみようと思った次第です。

# 自作「正方形さん」の使い方
前置きが長くなってしまいました。
環境とソースを見ていきます。

(2022/10/15 Githubにもアップしました。https://github.com/f18c052f/PhotoSquaringApp)

スクリプトを動かすにはディレクトリを下記の通りにしてください。

>[作業ディレクトリ]/
>  ├ MrSquare.ipynb
>  ├ img/
>  │  └ [変換したい画像]
>  └ output/

あらかじめ、imgフォルダ内に変換したい画像を配置しておき、スクリプト(MrSquare.ipynb)を実行します。
すると、一括で画像が変換され、outputフォルダ内に変換後画像が保存されます。

筆者はJupyter NoteBookで動かしていたため.ipynbとなっていますが、ご自分の環境に合わせて変更してください。

スクリプトはこちら。

```python:MrSquare
# import modules
import cv2
import os
import glob
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

# 正方形さん
class MrSquare:
    
    def __init__(self,
                 path_load='./img/', path_out='./output/',
                 fill_value=255,
                 resol_width = 1080, resol_height = 1080):
        """
        画像が正方形になるように白埋めする。

        【Parameters】
        ----------
        path_load : str
            読み込み対象ファイルが入っているディレクトリ
            
        path_out : str
            画像の出力先ディレクトリ
            
        fill_value : uint8
            余白を埋める色(グレースケール)
            Example)255:白, 0:黒
            
        resol_width : uint
            出力画像の幅
            
        resol_height : uint
            出力画像の高さ
            
        【Functions】
        ----------
        Test :
            指定インデックスの変換後画像を表示して確認
            ※画像は出力されない
        Run :
            ファイルを一括変換して保存
        
        """
        
        self.path_load = path_load 
        self.path_out = path_out 
        self.fill_value = fill_value
        self.resol_width = 1080
        self.resol_height = 1080
        
        self.run_flag = False

        self.img_lst = glob.glob(self.path_load + '*')
        print('Target files are as follows.\n')
        for name in self.img_lst:
            print(name + '\n')
         
    
    def Test(self, index=0):
        print('-- Test Mode --\n')
        self.run_flag = False
        
        try:
            self.__squaring(self.img_lst[index])
        except:
            print('※※※ 指定できるインデックスの範囲を超えています ※※※')
        
        
    def Run(self):
        print('-- Run Mr.Square --\n')
        self.run_flag = True
        
        for file in self.img_lst:
            self.__squaring(file)
        print('-- All Completed!!! --\n')
        
        
    def __squaring(self, file):
        
        try:
            img = cv2.imread(file)
        except:
            print('※※※ LOAD ERROR ※※※')
            
        img_w = img.shape[1]
        img_h = img.shape[0]
        img_cannel = img.shape[2]
        
        # キャンバスの生成
        canvas = np.full([self.resol_width, self.resol_height, img_cannel],
                         fill_value=self.fill_value,
                         dtype='uint8')
         
        if img_w >= img_h:
            
            # 画像が横長の場合
            dst_w = self.resol_width
            dst_h = round(img_h* (self.resol_width / img_w))
            top_edge = abs(round(self.resol_height/2 - dst_h))
            left_edge = 0
            
        else:
            
            # 画像が縦長の場合
            dst_w = round(img_w* (self.resol_height / img_h))
            dst_h = self.resol_height
            top_edge = 0
            left_edge = abs(round(self.resol_width/2 - dst_w))
            
        
        dst = cv2.resize(img, dsize=(dst_w, dst_h))
        out = canvas.copy()
        out[top_edge:top_edge + dst_h , left_edge:left_edge + dst_w] = dst
        
        
        
        filename = os.path.split(file)[-1]
        
        if self.run_flag:
            # Run Mode
            self.__saveImage(out, filename)
            
        else:
            # Test Mode
            self.__plotImage(img, out, filename)
        
        
    def __saveImage(self, output_img, filename):
        # 画像を保存
        cv2.imwrite(os.path.join(self.path_out, 'Resize_'+filename), output_img)
        print('Conversion Completed : ' + filename + '\n')
        
        
    def __plotImage(self, input_img, output_img, filename):
        # 画像をプロット
        fig, ax = plt.subplots()
        ax.imshow(cv2.cvtColor(input_img, cv2.COLOR_BGR2RGB))
        ax.set_title('Original')

        fig, ax = plt.subplots()
        ax.imshow(cv2.cvtColor(output_img, cv2.COLOR_BGR2RGB))
        ax.set_title('Converted')
        print('Image Name : ' + filename)

        plt.show() 

# 実行
engine = MrSquare()
engine.Run()
```

MrSquareクラスの各パラメータ・メソッドについては__init__下のコメントに記載されている通りドキュメント化してあるので、中身を知りたい方はご参考に。
![スクリーンショット 2022-05-21 21.29.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2649917/9bc2daa6-c05a-65c4-deca-28095a62e307.png)

ちなみにですが、Instagramの対応・推奨画像サイズは1080[px]×1080[px]らしいので、その値を出力画像解像度のデフォルト値としています。

上記スクリプトの通り、
```python:
# 実行
engine = MrSquare()
engine.Run()
```
とすれば、一括で画像を変換してくれます。
```python:実行結果
Target files are as follows.
./img\DSC01164.jpg
./img\DSC01207.jpg
./img\DSC02419 (1).jpg
./img\IMGP9401.jpeg
-- Run Mr.Square --
Conversion Completed : DSC01164.jpg
Conversion Completed : DSC01207.jpg
Conversion Completed : DSC02419 (1).jpg
Conversion Completed : IMGP9401.jpeg
-- All Completed!!! --
```
imgフォルダ内あった画像が変換されてoutputフォルダ内に画像が保存されています。
![スクリーンショット 2022-05-21 214453.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2649917/93afc24d-610f-400c-4588-2f870878bc3c.png)
![スクリーンショット 2022-05-21 214333.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2649917/58c7504d-a55b-8571-20fc-b3bee42d4093.png)



「正方形さん」の機能としては以上ですが、他にもいくつか実装した細かい部分について以下でご紹介しておきます。
## 余白色の設定
通常、余白を白で埋める設定になっていますが、グレースケール範囲で設定できるようにしてあります。

MrSquareクラスのfill_valueパラメータでいじれます。
例えば、
```python:
# 実行
engine = MrSquare(fill_value=0)
engine.Run()
```
とすることで、黒埋めされた画像が出力されます。
![スクリーンショット 2022-05-21 215111.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2649917/625d750f-8c45-ca47-343e-a2b187d8a8ae.png)


## テストモード
一括変換する前に個別の写真のビフォーアフターを確認しておきたい、となったときに使用します。

Testメソッドにインデックス番号を渡してあげることで確認できます。
```python:
# 実行
engine = MrSquare()
engine.Test(2)
```
実行結果はこんな感じ。
![スクリーンショット 2022-05-21 215654.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2649917/301eecc6-ef44-dd1b-ae05-2b3167f36115.png)