0
1

More than 3 years have passed since last update.

クラッシュオブクラウンと画像解析(1)

Last updated at Posted at 2020-12-13

やりたいこと

スマホゲームのクラッシュオブクラウン(以下クラクラ)において,対戦の配置画像からいろいろなこと(攻撃施設の密度の可視化や配置の弱点など)が分かりやすくなれば面白いかなと思ったので,pythonの勉強も兼ねて配置画像を解析したいと思います.今回はpythonのOpenCVを用いて入力画像の前処理までしたいと思います.

0.環境

MacOS
VScode 1.52.0
Python 2.7.16
numpy==1.18.1
opencv-python==4.4.0.46

1.対戦配置について

配置は以下の写真のようなものを想定しています.レイアウトエディタから撮影モードにしてスクリーンショトしたものです.
(この配置はQueen walkersのかたが配られたもので,それを拝借しています.)
IMG_6807A4D68BD8-1.jpeg

2.画像の加工

クラクラの配置のフォーマットは$48 \times 48$のマス目状になっているため,まずは菱形上の配置を正方形にする必要があります.そこで以下のコードを実行しました.

import numpy as np
import cv2

def resize_image():
  # 入力画像の読み込み
  pictrue = 'picture/GAKU2020.jpeg'
  img = cv2.imread(pictrue, cv2.IMREAD_COLOR)

  # 高さを定義
  height = img.shape[0]                         
  # 幅を定義
  width = img.shape[1]  
  # 回転の中心を指定                          
  center = (int(width/2), int(height/2))
  # 回転角度
  angle = 45.0
  # 拡大・縮小率
  scale = 1.0

  # 画像の横方向を縮小する
  img = cv2.resize(img , (int(width*0.75), height))
  # 画像を回転
  trans = cv2.getRotationMatrix2D(center, angle , scale)
  img2 = cv2.warpAffine(img, trans, (width,height))

 #画像の保存
 cv2.imwrite('./picture/GAKU2020_resize.png',img2)

if __name__ == '__main__':
  img2 = resize_image()


すると以下のような画像を得ることができます.
GAKU2020_resize.png

3.画像のトリミング

画像を正方形にすることができました.次にいらない部分のトリミングを行いたいと思います.欲しい画像は下地が緑色の部分なので,緑の部分を抽出しようと考えました.実装したコードは以下の通りです.(ほぼ参考したコードですが)

def detect_green_color(img):
    # HSV色空間に変換
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

    # 緑色のHSVの値域1
    hsv_min = np.array([20, 64, 0])
    hsv_max = np.array([90,255,255])

    # 緑色領域のマスク(255:赤色、0:赤色以外)    
    mask = cv2.inRange(hsv, hsv_min, hsv_max)

    # マスキング処理
    masked_img = cv2.bitwise_and(img, img, mask=mask)

    return mask, masked_img

参考:https://algorithm.joho.info/programming/python/opencv-color-detection/

得られたmaskの画像がこちらです.欲しい部分の正方形がなんとなく分かるようになったと思います.
green_mask.png

4.正方形の検出

最後に上の画像から配置の部分の正方形を検出したいと思います.正方形を検出するコードが検索すると出てきたので,とりあえずそれをそのまま実行してみました.すると,以下のような画像が得られました.

参考:https://www.366service.com/jp/qa/886f2ccd5feba07ec3b78b49744ceb4b
GAKU2020_all.png

たくさん正方形が検出されました.しかも,若干ずれています.そこで複数検出した正方形のうち一番大きいものを選び,それを縦横の座標を整えて出力するようなものを実装しました.以下がそのコードです.
もっといい方法があると思いますが,思いつかなかったので頭が悪そうな書き方になってしまいました…

import numpy as np
import cv2


def resize_image():
    ...

def detect_green_color(img):
    ...

# resize_imageの続き
def trim_image(img2):
  # 緑の部分を抽出
  green_mask, green_masked_img = detect_green_color(img2)
  cv2.imwrite("./picture/green_mask.png", green_mask)
  #cv2.imwrite("./picture/green_masked_img.png", green_masked_img)

  square = find_squares(green_mask)
  print(square)
  cv2.drawContours(img2, square, -1, (0, 255, 0), 3)

  # 検知した領域を表示
  cv2.imwrite('./picture/GAKU2020_a.png', img2)


# cosの計算
def angle_cos(p0, p1, p2):
    d1, d2 = (p0-p1).astype('float'), (p2-p1).astype('float')
    return abs( np.dot(d1, d2) / np.sqrt( np.dot(d1, d1)*np.dot(d2, d2) ) )

# 正方形の検出
def find_squares(img):
    img = cv2.GaussianBlur(img, (5, 5), 0)
    squares = []
    for gray in cv2.split(img):
        for thrs in range(0, 255, 26):
            if thrs == 0:
                bin = cv2.Canny(gray, 0, 50, apertureSize=5)
                bin = cv2.dilate(bin, None)
            else:
                _retval, bin = cv2.threshold(gray, thrs, 255, cv2.THRESH_BINARY)
            contours, _hierarchy = cv2.findContours(bin, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
            for cnt in contours:
                cnt_len = cv2.arcLength(cnt, True)
                cnt = cv2.approxPolyDP(cnt, 0.02*cnt_len, True)
                if len(cnt) == 4 and cv2.contourArea(cnt) > 1000 and cv2.isContourConvex(cnt):
                    cnt = cnt.reshape(-1, 2)
                    max_cos = np.max([angle_cos( cnt[i], cnt[(i+1) % 4], cnt[(i+2) % 4] ) for i in range(4)])
                    if max_cos < 0.1:
                        squares.append(cnt)

    max_length = 0
    largest_square = None
    for square in squares:
      # 全ての座標の読み込み
      x0, y0 = square[0]
      x1, y1 = square[1]
      x2, y2 = square[2]
      x3, y3 = square[3]

      # 横と縦の長さの平均
      x_length = int(1/2 * (abs(x1 - x2) +  abs(x3 - x0)))
      y_length = int(1/2 * (abs(y0 - y1) +  abs(y2 - y3)))
      length = int(1/2*(x_length+y_length))

      # 新しい座標(仮)
      x0_new = int(1/2 * (x0 + x1)) 
      x1_new = int(1/2 * (x2 + x3))
      y0_new = int(1/2 * (y1 + y2))
      y1_new = int(1/2 * (y0 + y3))
      #print('*old*')
      #print(y0_new)
      #print(y1_new)

      # 横と縦の長さを揃えるための処理
      x_len = x1_new - x0_new
      y_len = y0_new - y1_new

      x_diff = length - x_len
      y_diff = length - y_len

      x0_new += int(x_diff/2)
      x1_new = x0_new + length
      y0_new += int(y_diff/2)
      y1_new = y0_new - length
      #print('*new*')
      #print(y0_new)
      #print(y1_new)

      x_len = x1_new - x0_new
      y_len = y0_new - y1_new

      # 新しい座標
      new_square = np.array([[x0_new, y1_new],
                            [x0_new, y0_new],
                            [x1_new, y0_new],
                            [x1_new, y1_new]])
    # 一番長い辺を持つ正方形を記憶
      if length > max_length:
        max_length = length
        largest_square = new_square

    # データ形状の微調整
    largest = []
    largest.append(largest_square)

    return largest


if __name__ == '__main__':
  img2 = resize_image()
  trim_image(img2)


得られた画像がこちらです.

GAKU2020_a.png

いい感じだけど,左と上が若干ずれているのが気になる.ズレた部分は10ピクセル程度で,配置の1マスが40ピクセル程度だからこれがどう響いてくるか…

5.最後に

今回は入力画像を整形して欲しい部分の抽出を行いました.
次は施設を検出できるかどうか試したいと思います.
$48 \times 48$マスのどこにあるか分かればいいから比較的簡単なはず(多分)

(訂正2020/12/29)マス目は$48 \times 48$でなく$44 \times 44$みたいでした.

0
1
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
1