【OpenCV】大迫力なスーパームーンのNutationを描画する♬



今朝が今年最近接の月;スーパームーン
しかし、お花茶屋つまりウワンが住んでいるところは昨夜が深夜まで雨、そして今もどんよりでついにスーパームーンは見えそうにない
上記の絵は、一昨日つまりスーパームーン前日のほぼ満月。。。
ということで、この10日余りの月の満ち欠けをGifアニメーションにしたので、それを書きたいと思う。
今回の成果は以下のGifアニメーションです。
(1024x4枚合成)

今回やったこと

・まず晴れたら月面写真を撮ります
・月のカメラ写真は曲がっている
・そしてGifにします

コードを以下に置きました

OpenCV/gazo_transrotate.py
Gif_Animation/out_gif.py

・まず晴れたら月面写真を撮ります

たくさん撮りました。

・月のカメラ写真は曲がっている

最初は気が付かなったのですが、たくさん撮って、いざ重ねようとすると方向が違う。
ということで、まず重なるように方向を変える必要があります。
今回使う技術は、領域認識、回転ー並進、拡大、縮小、そしてGifアニメーション作成です。

ここで、OpenCV等を使って以下のようなコードを書きました。
利用しているものは以下のようなものです。

from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
from PIL import ImageOps
import cv2

まず、画像を読み込んでGaussianBlurで若干の平滑化をし、大津の二値化により白黒画像を作成します。そして、それを利用して領域認識を行います。
【参考】
大津の二値化

# 元となる画像の読み込み
filename='4378-2'
img = cv2.imread(filename+'.jpg',0)
blur = cv2.GaussianBlur(img,(5,5),0)
ret3,thresh = cv2.threshold(blur,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
plt.imshow(thresh)
plt.pause(1)
plt.close()


そしてimgEdge,contours,hierarchy = cv2.findContours(thresh, 1, 2)により、edgeを抽出し、領域contoursを抽出します。

imgEdge,contours,hierarchy = cv2.findContours(thresh, 1, 2)

#抽出された輪郭群をひとつひとつ処理
i = 0
radius0=0
for c in contours :
    cnt = c
    M = cv2.moments(cnt)
    #面積を求める
    area = cv2.contourArea(cnt)

以下のように一定の大きさの領域だけを残します。
※ここでは実際は最大の領域を抽出したいと思っています

    #ゴミを除外
    if area < 1000 : 
        continue

抽出された円を描画します。


    # 円描画に必要な情報を計算
    (x,y),radius = cv2.minEnclosingCircle(cnt)
    center = (int(x),int(y))
    radius = int(radius)
    print(center,radius)
    img = cv2.circle(img,center,radius,(125,125,125),5)
    plt.imshow(img)
    plt.show()
    plt.close()

結構、きれいに抽出できました。やはり、ほんのちょっと(約1日分)かけているのがわかります。

以下のコードで最大の半径を持つ円の中心を求めています。

    if radius >= radius0:
        center_x=center[0]
        center_y=center[1]
        radius0=radius

以下のコードで物体の外接矩形を求めていますが、今回は使いませんでした。
※半月の時抽出しても画像の回転中心が求められない

    x,y,w,h = cv2.boundingRect(cnt)

以下、この円を利用して月を回転させます。
そのために、新たに元画像を読み込みます。
そして、並進ー回転を行います。
※今回は並進はしなくてもいいですが、一応並進もさせました

j=-1
for x in range(-20,20,10):
    for y in range(-20,20,10):
        img = Image.open(filename+'.jpg')
        img = img.rotate(0, translate=(x, y))
        img = img.rotate(0,center=(center_x+x, center_y+y))
        plt.imshow(img)

画像を一回一回保存します。

        j += 1
        plt.savefig('./rotate/transrotate_4378/trans_rotate_4378_'+str(j)+'.jpg')
        plt.pause(0.01)
        plt.close()
        for i in range(10,361,10):
            j += 1
            img = img.rotate(10,center=(center_x+x, center_y+y))
            plt.imshow(img)
            plt.savefig('./rotate/transrotate_4378/trans_rotate_4378_'+str(j)+'.jpg')
            plt.pause(0.01)
            plt.close()

ということで、数百枚の画像が生成されます。
ここから、方向のあったものを寄せ集めて、Gifアニメーションを作成しました。
実際には相関係数でぴったり合わせようと思っていましたが、それは今回は実施できませんでした。
※この解像度だと時間がかかりすぎます

・そしてGifにします

Gifアニメーションのコードは以下のとおりです。
※2048は容量制限で載せられませんでしたが、。。上の絵は1024のものです

from PIL import Image, ImageDraw

s=5
images = []
for i in range(1,s,1):
    im = Image.open('./rotate/123/'+str(i)+'.jpg') 
    im =im.resize(size=(2048, 2048), resample=Image.NEAREST)  #- NEAREST - BOX - BILINEAR - HAMMING - BICUBIC - LANCZOS
    images.append(im)
images[0].save('original_1234_2048NEAREST.gif', save_all=True, append_images=images[1:s], duration=100*10, loop=0)   

ということで、以下のとおり何個か作ってみました。


こうして眺めてみると、月のNutationが見えているかなというところです。
満月に近づくに従い、左斜め下の〇の部分の月が少しだけこっち向いています。
【参考】
月は秤動という首振り運動をしているので地球からは月の全表面の59%を見ることが可能

なお、上記参考に加えて今回以下のサイトを参考にしています。
【参考】
OpenCV-CookBook »画像処理
OpenCV-Python Tutorials 1 documentation » OpenCV-Pythonチュートリアル » OpenCVを使った画像処理 » OpenCVにおける輪郭(領域)
Python, Pillowで画像を回転する[email protected]
【Python】Pillowで画像をresizeするときの処理時間の違い

まとめ

・スーパームーンのNutationをGifアニメーションにした
・大津の二値化を利用して画像領域を抽出して、回転により方向をそろえた

・画像の相関係数を計算することにより、もう少し精度を追求したい