【Python-openCV】画像を3×3のパズルして遊んでみた。

こんにちは、ヒガシです。

 

今回はPython-openCVを使って以下の動画のように、任意の画像をパズル化してみましたのでご紹介させていただきます。

 

こんなものが作れたとしても業務には何の役にも立たないと思いますが、こいつを作るには様々なPython-openCVのスキルが必要とされます。

 

ぜひ処理を追いかけてみて、どうやったらできるのか、どんなことをしているのか参考にしてみてください。

 

それではさっそくやっていきましょう。

 

スポンサーリンク

任意の画像をパズル化するサンプルコード

いきなりですが、以下が全体コードです。

#ライブラリインポート
import cv2
import numpy as np
import random
#読み込むファイル名を指定
file_name='sample_pic.jpg'
#画像読み込み
img=cv2.imread(file_name,cv2.IMREAD_COLOR)
h,w=img.shape[:2]
#画像の分割数指定、パズル用の情報入力
splitx=3
splity=3
counter=0
#クリックした場所が何枚目なのか確認するための配列
posx_list=[w/6,w/2,w*5/6]
posy_list=[h/6,h/2,h*5/6]
#移動可能な場所の指定(1で移動)
pos0=[100,1,0,1,0,0,0,0,0]
pos1=[1,100,1,0,1,0,0,0,0]
pos2=[0,1,100,0,0,1,0,0,0]
pos3=[1,0,0,100,1,0,1,0,0]
pos4=[0,1,0,1,100,1,0,1,0]
pos5=[0,0,1,0,1,100,0,0,1]
pos6=[0,0,0,1,0,0,100,1,0]
pos7=[0,0,0,0,1,0,1,100,1]
pos8=[0,0,0,0,0,1,0,1,100]
pos_available=[pos0,pos1,pos2,pos3,pos4,pos5,pos6,pos7,pos8]

#ベース画像分割関数
def split_pic(img):
    h,w=img.shape[:2]
    cx,cy=0,0
    pic_list=[]
    for j in range(splitx):
        for i in range(splity):
            spic=img[cy:cy+int(h/splity),cx:cx+int(w/splitx),:]
            pic_list.append(spic)
            cy+=int(h/splity)
        cy=0
        cx+=int(w/splitx)
    pic_list=np.array(pic_list)
    return pic_list

#クリック後の画像アップデート関数
def pic_apdate(pic_list,pos_list):
    base_img=np.zeros((h,w,3),np.uint8)
    cx,cy=0,0
    for j in range(splitx):
        for i in range(splity):
            ppp=splitx*j+i
            if pos_list[ppp]!=100:
                base_img[cy:cy+int(h/splity),cx:cx+int(w/splitx),:]=pic_list[pos_list[ppp]]
            cy+=int(h/splity)
        cy=0
        cx+=int(w/splitx)
    return base_img

#分割画像のポジション情報のアップデート
def pos_update(pos_index,pos_list,kkk):
    if pos_available[pos_list.index(100)][pos_index]==1:
        pos_list[pos_list.index(100)]=pos_list[pos_index]
        pos_list[pos_index]=100
        kkk+=1
    return pos_list,kkk


#パズルゲームのメイン部分
def play_pazzle(event, x, y, flags, params):
    global counter,img2,pos_list,kkk
    img2=np.copy(img)
    pic_list=split_pic(img)
    #初期化
    if event == cv2.EVENT_LBUTTONDOWN and counter==0:
        pos_list=[0,1,2,3,4,5,6,7,100]
        counter=1
        pos_index=0
        #ベース画像からランダムにマス目を移動させる
        kkk=0
        while kkk<100:
            pos_list,kkk=pos_update(pos_index,pos_list,kkk)
            pos_index=np.random.randint(0,splitx*splity)  
        new_pic=pic_apdate(pic_list,pos_list)
        cv2.imshow('window', new_pic)
    #マス目移動処理  
    elif event == cv2.EVENT_LBUTTONDOWN and counter==1:
        posx=np.argmin(np.abs(np.array(posx_list)-x))
        posy=np.argmin(np.abs(np.array(posy_list)-y))
        pos_index=posx*splitx+posy
        pos_list,kkk=pos_update(pos_index,pos_list,kkk)
        new_pic=pic_apdate(pic_list,pos_list)
        cv2.imshow('window', new_pic)
        if pos_list==[0,1,2,3,4,5,6,7,100]:
            cv2.putText(img2, 'Complete!!',(int(w/2)-150, int(h/2)+50),cv2.FONT_HERSHEY_SIMPLEX,fontScale=2,color=(0,0,0),thickness=6)
            cv2.imshow('window', img2)
            counter=0
        
#画像に番号をふっておく処理(なくてもOK)
h,w=img.shape[:2]
for j in range(splitx):
    for i in range(splity):
        num=j*splitx+i
        cv2.putText(img, str(num) ,(int(w/splitx*j) +100, int(h/splity*i) +100),cv2.FONT_HERSHEY_SIMPLEX,fontScale=1.0,color=(255,0,0),thickness=2)
cv2.imwrite('aaa.jpg',img)

cv2.imshow('window', img)
cv2.setMouseCallback('window', play_pazzle)
cv2.waitKey(0)
cv2.destroyAllWindows()

かなり長いですね・・・。

 

これだけみてもよくわからないと思いますので、以降で各ブロックの簡単な説明をしておきます。

スポンサーリンク

パズルコードの解説1:情報入力

まずは以下のコード部分を説明します。

#ライブラリインポート
import cv2
import numpy as np
import random
#読み込むファイル名を指定
file_name='sample_pic.jpg'
#画像読み込み
img=cv2.imread(file_name,cv2.IMREAD_COLOR)
h,w=img.shape[:2]
#画像の分割数指定、パズル用の情報入力
splitx=3
splity=3
counter=0
#クリックした場所が何枚目なのか確認するための配列
posx_list=[w/6,w/2,w*5/6]
posy_list=[h/6,h/2,h*5/6]
#移動可能な場所の指定(1で移動)
pos0=[100,1,0,1,0,0,0,0,0]
pos1=[1,100,1,0,1,0,0,0,0]
pos2=[0,1,100,0,0,1,0,0,0]
pos3=[1,0,0,100,1,0,1,0,0]
pos4=[0,1,0,1,100,1,0,1,0]
pos5=[0,0,1,0,1,100,0,0,1]
pos6=[0,0,0,1,0,0,100,1,0]
pos7=[0,0,0,0,1,0,1,100,1]
pos8=[0,0,0,0,0,1,0,1,100]
pos_available=[pos0,pos1,pos2,pos3,pos4,pos5,pos6,pos7,pos8]

 

この部分では、前半部分でライブラリのインポート&画像読み込み、分割数指定を行っています。

 

また、posx_list、posy_listでは遊んでいるときにクリックした場所が左から(上から)何番目のマスなのかを確認するときに使用する配列です。

 

そしてこの部分でもっとも重要なのが、pos0,pos1・・・といった配列が大量に指定されているところです。

 

この配列は、各マスに空白が来た際に、どこのマスとなら空白部を交換できるかを指定したものです。

 

例えば以下の画像の0の部分に空白が来た場合、交換できるのは1か3ですよね?

画像のマス目の解説

pos0=[100,1,0,1,0,0,0,0,0]では100が今空白にいることを表し、1が交換可能な場所、0が交換不可能な場所を表しています。

 

基本的にこの情報をもとにパズルゲームをすすめていくことになります。

 

スポンサーリンク

パズルコードの解説2:サブ関数の定義

次は以下の部分の解説を行います。

#ベース画像分割関数
def split_pic(img):
    h,w=img.shape[:2]
    cx,cy=0,0
    pic_list=[]
    for j in range(splitx):
        for i in range(splity):
            spic=img[cy:cy+int(h/splity),cx:cx+int(w/splitx),:]
            pic_list.append(spic)
            cy+=int(h/splity)
        cy=0
        cx+=int(w/splitx)
    pic_list=np.array(pic_list)
    return pic_list

#クリック後の画像アップデート関数
def pic_apdate(pic_list,pos_list):
    base_img=np.zeros((h,w,3),np.uint8)
    cx,cy=0,0
    for j in range(splitx):
        for i in range(splity):
            ppp=splitx*j+i
            if pos_list[ppp]!=100:
                base_img[cy:cy+int(h/splity),cx:cx+int(w/splitx),:]=pic_list[pos_list[ppp]]
            cy+=int(h/splity)
        cy=0
        cx+=int(w/splitx)
    return base_img

#分割画像のポジション情報のアップデート
def pos_update(pos_index,pos_list,kkk):
    if pos_available[pos_list.index(100)][pos_index]==1:
        pos_list[pos_list.index(100)]=pos_list[pos_index]
        pos_list[pos_index]=100
        kkk+=1
    return pos_list,kkk

まずひとつめのsplit_pic関数では、先ほど指定したベース画像を3×3の9つの画像データに分割処理を行います。

この分割された画像達を一つ一つ配置していくことでパズル感を出していきます。

 

次に3つめのpos_updateについてです。

今回のパズルゲームでは現在のマス状態の情報はすべてpos_listという変数に格納されています。

仮に画像内の交換可能な場所がクリックされた場合、その場所と空白を入れ替える必要がありますので、その情報アップデートをこの関数の中で行っています。

 

交換可能な場所かどうかは最初に指定したpos_available変数を確認しながらおこなっています。

 

最後にpic_update関数についてです。

ここは先ほどアップデートされたマスの状態情報をもとにsplit_pic関数で作った分割画像をわりあてているだけですね。

 

スポンサーリンク

パズルコードの解説3:メインの関数定義

最後に、ここがパズルゲームのメイン部分です。

#パズルゲームのメイン部分
def play_pazzle(event, x, y, flags, params):
    global counter,img2,pos_list,kkk
    img2=np.copy(img)
    pic_list=split_pic(img)
    #初期化
    if event == cv2.EVENT_LBUTTONDOWN and counter==0:
        pos_list=[0,1,2,3,4,5,6,7,100]
        counter=1
        pos_index=0
        #ベース画像からランダムにマス目を移動させる
        kkk=0
        while kkk<100:
            pos_list,kkk=pos_update(pos_index,pos_list,kkk)
            pos_index=np.random.randint(0,splitx*splity)  
        new_pic=pic_apdate(pic_list,pos_list)
        cv2.imshow('window', new_pic)
    #マス目移動処理  
    elif event == cv2.EVENT_LBUTTONDOWN and counter==1:
        posx=np.argmin(np.abs(np.array(posx_list)-x))
        posy=np.argmin(np.abs(np.array(posy_list)-y))
        pos_index=posx*splitx+posy
        pos_list,kkk=pos_update(pos_index,pos_list,kkk)
        new_pic=pic_apdate(pic_list,pos_list)
        cv2.imshow('window', new_pic)
        if pos_list==[0,1,2,3,4,5,6,7,100]:
            cv2.putText(img2, 'Complete!!',(int(w/2)-150, int(h/2)+50),cv2.FONT_HERSHEY_SIMPLEX,fontScale=2,color=(0,0,0),thickness=6)
            cv2.imshow('window', img2)
            counter=0
        
#画像に番号をふっておく処理(なくてもOK)
h,w=img.shape[:2]
for j in range(splitx):
    for i in range(splity):
        num=j*splitx+i
        cv2.putText(img, str(num) ,(int(w/splitx*j) +100, int(h/splity*i) +100),cv2.FONT_HERSHEY_SIMPLEX,fontScale=1.0,color=(255,0,0),thickness=2)
cv2.imwrite('aaa.jpg',img)

cv2.imshow('window', img)
cv2.setMouseCallback('window', play_pazzle)
cv2.waitKey(0)
cv2.destroyAllWindows()

 

やっていることとしては、最初に読み込んだ画像をWindowに表示させ、その画像内をクリックすることで画面がシャッフルされゲームが開始、2回目以降のクリックで先ほど定義した関数を駆使して画面を更新していくという流れです。

 

そしてポジションが正解になったらCOMPLETE!!の文字を表示させてリセットするという感じです。

 

なかなか難しかったと思いますが、解説は以上です。

 

スポンサーリンク

おわりに

というわけで今回はpython-openCVを使って、任意の画像からパズルゲームを作成する方法をご紹介しました。

 

ぜひお子様とのお遊びなんかにご活用ください。

 

このように、私のブログでは様々なスキルを紹介しています。

過去記事一覧

 

今は仕事中で時間がないかもしれませんが、ぜひ通勤時間中などに他の記事も読んでいただけると嬉しいです。
⇒興味をもった方は【ヒガサラ】で検索してみてください。

確実にスキルアップできるはずです。

 

最後に、この記事が役に立ったという方は、ぜひ応援よろしくお願いします。
↓ 応援ボタン
にほんブログ村 IT技術ブログへ
にほんブログ村

それではまた!

コメント

タイトルとURLをコピーしました