2019年5月16日木曜日

AV女優9852人のデータから自分が出演できそうなAVのジャンルを予測する

ご存知と思いますが,一応音楽活動なんぞしているもんで,しているからにはやっぱり有名になりたいという気持ちが少なからずあります.

有名になる方法はいろいろあると思いますが.その中の一つとして,AVに素人として出演して,炎上するという方法があります.筆者は男性ですが,この方法を採るかどうかは別として,AVに出演するとしたらどのようなジャンルのAVに出演できる可能性が高いか,現在日本最大級のアダルトポータル FANZA(DMM.R18)で販売されている作品に出演しているAV女優のデータをスクレイピングし,機械学習によって予測してみます.



1, AV女優の情報をスクレイピング


この処理は,似たようなことをやっている方の記事があったので,それをそのまま使い,本プロジェクトに足りない部分を追加しました.

【pythonコードを読む】AV女優10000人webスクレイピング

上記の記事では,AV女優の名前,顔写真のみ取得していますが,本プロジェクトでは,

名前,顔写真URL,出演作品数,作品一覧ページのURL,どのジャンルに何回出演したか

を取得しています.
コードは以下


from selenium import webdriver
import pandas as pd
import math
import collections
 
data = {"name": [],
        "image":[],
        "出演数":[],
        "URL":[]
}
df = pd.DataFrame(data)
 
url = 'http://www.dmm.co.jp/digital/videoa/-/actress/='
driver = webdriver.Chrome()
driver2=webdriver.Chrome()
driver3=webdriver.Chrome()
next_key = '#main-ds > div.d-area.area-actbox > div:nth-child(3) > p'
 
boins=['a', 'i', 'u', 'e', 'o']
siins=['','k','s','t','n','h','m','y', 'r', 'w']
print("test")
def function(df):
    #siin + boinで始まる女優の一覧ページ
    driver.get(url + '/keyword=' + siin + boin)
    ninzu = driver.find_element_by_css_selector(next_key).text
    #ページ数を取得
    num = -(-int(list(map(str,ninzu.split("\u3000")))[0][0:-2])//100)
    print(num)
    for i in range(num):
 #boin +siin で始まる女優一覧の num ページ目
        driver.get(url + '/keyword=' + siin + boin + '/page=' + str(i+1))
        #出演作品数
        actcount=driver.find_element_by_css_selector("#main-ds > div.d-area.area-actbox > div.d-sect.act-box").find_elements_by_tag_name("span")
        #各女優欄
        elements = driver.find_element_by_css_selector("#main-ds > div.d-area.area-actbox > div.d-sect.act-box").find_elements_by_tag_name("img")
        n=0
        for element in elements:
            genrelist=[]
     #女優名
            src = element.get_attribute("src")
            #画像URL
            alt = element.get_attribute("alt")
            #出演作品一覧URL
            actressurl=driver.find_element_by_css_selector("#main-ds > div.d-area.area-actbox > div.d-sect.act-box > ul > li:nth-child("+str(n+1)+") a").get_attribute("href")
            j=0
     #作品一覧のページ数
            videopagenum=math.floor(float(actcount[2*n+1].text.split(":")[1])/120)+1
            for j in range(videopagenum):
                try:
      #作品一覧URLの(J+1)ページ目を開く
                    driver2.get(actressurl+'page='+str(j+1)+'/')
                    #各作品欄をリストする
                    videos=driver2.find_element_by_css_selector("#main-src > div.d-area > div.d-sect > div.d-item").find_elements_by_tag_name("img")
                    k=1
                    for video in videos:
   #各作品のURL
                        videourl=driver2.find_element_by_css_selector("#list > li:nth-child("+str(k)+") > div > p.tmb > a").get_attribute("href")
                        k+=1
                        driver3.get(videourl)
   #ジャンル欄をリスト
                        genres=driver3.find_element_by_css_selector("#mu > div > table > tbody > tr > td:nth-child(1) > table > tbody > tr:nth-child(11) > td:nth-child(2)").find_elements_by_tag_name("a")
                        #ジャンルをリストに追加していく
                        for genre in genres:
                            genrelist.append(genre.text)
                except:
                    pass
            se = pd.Series([alt,src,actcount[2*n+1].text.split(":")[1],actressurl,collections.Counter(genrelist)],["name", "image","出演数","URL","ジャンル"])
            print(alt+": "+str(collections.Counter(genrelist)))
            df = df.append(se, ignore_index=True)
            n+=1
    return df
 
 
for siin in siins:
    if siin == 'y':
        for boin in boins[::2]:
            df=function(df)
    elif siin == 'w':
        for boin in boins[0]:
            df=function(df)
    else:
        for boin in boins:
            df=function(df)
print("output")
df.to_csv("final.csv")
driver.quit()
driver2.quit()

全女優の全作品のページ全てに順番にアクセスしていくので,時間が恐ろしくかかります.筆者は1ヶ月弱かかりました.この間にCIFAR-100でうまく学習できるようにKerasのモデルを準備したりしておきましょう.

保存したデータはこのような形式になります.ジャンルの列は,collections.Counter(genrelist)の結果をそのまま文字列にして記録しているので,初めにConterとかが付いています.利用するときは,文字列処理を実装して,必要な情報を抜き出す必要があります.(本記事でも後に行なっています)


2, ラベリングしたいジャンルを限定する


FANZAで作品タグとして存在しているジャンルは,画像のように大量に存在します.


このジャンル全てを識別するモデルを作成するのはかなり困難だと考えられます.また,意外と多いタグ

期間限定配信
独占配信
スマホ限定配信

こんなタグは,「AVのジャンル」としてはほとんど意味がありません.

そこで,上の画像のジャンルの中から「シチュエーション」にカテゴライズされているジャンルに限定して学習データを作成します.とりあえず,「シチュエーション」の中のジャンルを列挙したcsvファイルを作成します.

シチュエーションのところにあるジャンルを選択し,


viに貼り付けます.
一番上の行に「ジャンル」と書いた行を追加して,"genre.csv"として保存します.




3, 学習用データセット作成


スクレイピングしたデータfinal.csvをもとに,ニューラルネットワークに学習させるデータを作成します.

基本的には,final.csvに記述されたAV女優の情報を1人1人読んでいき,更に,ジャンル欄の中で列挙したジャンルも一つ一つ読んでいきます.この欄は,ジャンルとその数を書いた文字列が一つの文字列データとしてまとまっているので,

Counter({'

までは切り捨て,コンマを区切りにしてジャンルを区切っていきます.

その一つ一つを

':

で区切り,その最初の要素をリストにすれば,その女優が出演していたジャンルがリストとして取得できます.

ここで,このリストは出演回数が多い順に並んでいるので,女優ごとのジャンルのリストを順に見ていき,最初にgenre.csvに列挙したジャンルのどれかと一致した時のジャンルを,その女優に最も適したジャンルとします.一致したジャンルを見つけられたら,顔写真をダウンロードします.

これを全女優に対して行い,女優1人に対して,1つのジャンル,1枚の顔写真というデータを作っていきます.

一致するジャンルが一つもなければ,その女優は残念ですが,データセットから外れていただきます.

ここで,大問題が発生しました.FANZAから取得できる顔写真は,ほとんどが100x100というサイズなのですが,たまに違うサイズの画像が混じっていたのです.そこで,ダウンロードした画像について,解像度をチェックし,100x100でなければ,データセットから外すようにしました.

ここまでのコードがこちら.


from PIL import Image
import pandas as pd
import requests
import shutil
import numpy as np

data={"ジャンル":[],"label":[]}
df=pd.DataFrame(data)

#ジャンル一覧csvファイルを読み込む
genrelist=pd.read_csv('genre.csv')
genretemp=genrelist['ジャンル']

actlist=pd.read_csv('final.csv')
name=actlist['Name']
url=actlist['image']
genre=actlist['ジャンル']
count=0
for n in range(len(name)):
 findgenre=0
 #最初のジャンル名のところ(10文字目)まで切り取る
 genre[n]=genre[n][10:len(genre[n])-2]
 pgenrelist=genre[n].split(', \'')
 for pgenre in pgenrelist:
  #': のところまでをジャンル名とする(一番最初に記述されているジャンル名)
  pgenre=pgenre.split('\':')
  
  #ジャンル一覧に一致したら,画像をダウンロード.画像が100x100であれば画像をdataフォルダに保存,そのジャンルリストに追加.
  for m in range(len(genretemp)):
   if genretemp[m]==pgenre[0]:  
    print(name[n])
    print(url[n])
    print(pgenre[0])
    r=requests.get(url[n],stream=True)
    if r.status_code==200:
                                        with open('temp.jpg','wb') as f:
                                                f.write(r.content)
    img=Image.open('temp.jpg')
    width,height=img.size
    if width==100 and height==100:
     se=pd.Series([int(m),pgenre[0]],["label","ジャンル"])
     df=df.append(se,ignore_index=True)
     shutil.copyfile('temp.jpg','data/'+str(count)+'.jpg')
     count=count+1
     findgenre=1
     break
  if findgenre==1:
   #一回ジャンル一覧と一致したら,次の女優を処理する
   break
df.to_csv("data.csv")

女優ごとのジャンル一覧,ダウンロードした各女優の顔写真を,Kerasに入力するndarrayに変換しなければなりません.この処理も非常に時間がかかりました.時間がかかったので,この部分だけ独立させて,作成したndarrayをファイルに保存することにしました.


from PIL import Image
import pandas as pd
import tensorflow as tf
from tensorflow import keras
import numpy as np
import matplotlib.pyplot as plt
from numpy.random import *

#ジャンル一覧csvファイルを読み込む
genrelist=pd.read_csv('genre.csv')
genrename=genrelist['ジャンル']

#ジャンルをリスト化したcsvファイルを読み込む
datagenrelist=pd.read_csv('data.csv')
train_labels=datagenrelist['label']
#機械学習で使うOne=Hotベクトルに変換.これが教師データのラベルデータとなる.
train_labels_onehot=keras.utils.to_categorical(train_labels,len(genrename))

#一旦空のndarrayを定義
train_images=np.empty((0,100,100,3))
#dataフォルダの画像をRGBデータにしてtrain_imagesに追加
for n in range(len(train_labels)):
 img=Image.open('data/'+str(n)+'.jpg')
 im=np.array(img)
 train_images=np.append(train_images,[im],axis=0)

#画像を0.0.-1.0の表現に変換
train_images=train_images.astype("float32")/255.0
#ndarrayをファイルとして保存
np.save('train_images.npy',train_images)
np.save('train_labels_onehot.npy',train_labels_onehot)

#少し表示してみる
plt.figure(figsize=(10,10))
for i in range(25):
 plt.subplot(5,5,i+1)
 plt.xticks([])
 plt.yticks([])
 imgindex=randint(len(train_labels))
 plt.imshow(train_images[imgindex],cmap=plt.cm.binary)

plt.show()

一部のAV女優の顔写真を少し表示して見ました.ここで正しいっぽく表示されているということは,Kerasに問題なく入力できそうです.本当は,顔の部分だけ切り出したりしたほうがいいと思いますが,今回は理由がありそれはやりません.
その理由とは,面倒くさいからです.



4, 学習

CIFAR-100を畳み込みニューラルネットワークで認識の時とほとんど同じです.


from PIL import Image
import pandas as pd
import tensorflow as tf
from tensorflow import keras
import numpy as np
import matplotlib.pyplot as plt
from numpy.random import *

genrelist=pd.read_csv('genre.csv')
genrename=genrelist['ジャンル']

datagenrelist=pd.read_csv('data.csv')
train_labels=datagenrelist['label']

train_images=np.load('train_images.npy')
train_labels_onehot=np.load('train_labels_onehot.npy')

#ネットワーク作成
model=keras.Sequential()

model.add(keras.layers.Conv2D(filters=100,kernel_size=(3,3),padding='same',activation='relu',input_shape=(100,100,3)))
model.add(keras.layers.Conv2D(filters=100,kernel_size=(3,3),padding='same',activation='relu'))
model.add(keras.layers.MaxPool2D(pool_size=(2,2)))
model.add(keras.layers.Dropout(0.25))

model.add(keras.layers.Conv2D(filters=64,kernel_size=(3,3),padding='same',activation='relu'))
model.add(keras.layers.Conv2D(filters=64,kernel_size=(3,3),padding='same',activation='relu'))
model.add(keras.layers.MaxPool2D(pool_size=(2,2)))
model.add(keras.layers.Dropout(0.25))

model.add(keras.layers.Flatten())
model.add(keras.layers.Dense(512,activation='relu'))
model.add(keras.layers.Dropout(0.5))
model.add(keras.layers.Dense(len(genrename),activation='softmax'))

model.compile(optimizer=tf.train.AdamOptimizer(),loss='categorical_crossentropy',metrics=["accuracy"])

#学習用データで学習してみる
model.fit(train_images[:,:,:,:],train_labels_onehot,epochs=30,batch_size=64)

#ネットワークをファイルに保存
model_json_str=model.to_json()
open('model.json','w').write(model_json_str)
#学習したパラメータをファイルに保存
model.save_weights('weights.h5')

学習結果はこんな感じです.


精度95.4%です.CIFAR-100の時より精度上がりましたね.
本来は,データセットの中で,チェック用のデータを学習に使ったデータとは別に用意して,どれだけの精度が出たかを調べるのですが,今回はデータセット全てを学習に使ったので,チェック用のデータがありません.なので,学習に使ったデータ自身を全てネットワークに入力してみましたが,予測結果は全て正解でした.まあ当たり前っちゃ当たり前ですが,少なくとも,全然出来ていない訳ではなさそうだということがわかりました.この部分は無理やり納得して,いよいよ最後の段階です.

5, 自分が出演できそうなAVのジャンルを予測


いよいよ予測します.OpenCVを使って入力した画像の中から,顔の部分を認識し,その部分を100x100にリサイズして入力します.

import sys
import pandas as pd
import tensorflow as tf
from tensorflow import keras
import numpy as np
import cv2
from PIL import Image
import matplotlib.pyplot as plt

genrelist=pd.read_csv('genre.csv')
genrename=genrelist['ジャンル']

datagenrelist=pd.read_csv('data.csv')
train_labels=datagenrelist['label']

train_images=np.load('train_images.npy')
train_labels_onehot=np.load('train_labels_onehot.npy')

#ネットワーク読み込み
model=keras.models.model_from_json(open('model.json').read())
#学習したパラメータ読み込み
model.load_weights('weights.h5')
model.summary()
model.compile(optimizer=tf.train.AdamOptimizer(),loss='categorical_crossentropy',metrics=["accuracy"])

#予測したい画像を読み込み,顔の部分を抜き出す
image=cv2.imread(sys.argv[1])
image=cv2.cvtColor(image,cv2.COLOR_RGB2BGR)
plt.figure()
plt.imshow(image)
cascade=cv2.CascadeClassifier('haarcascade_frontalface_alt.xml')
face_list=cascade.detectMultiScale(image,scaleFactor=1.1,minNeighbors=1,minSize=(100,100))
if len(face_list)>0:
    for rect in face_list:
        image=image[rect[1]:rect[1]+rect[3],rect[0]:rect[0]+rect[2]]

#画像を100x100にリサイズ
image=cv2.resize(image,(100,100))
plt.figure()
plt.imshow(image)
#画像を0.0 - 1.0の間に正規化
image=image.astype("float32")/255.0
#予測
label=model.predict(np.array([image[:,:,:]]))
predict=np.argmax(label)
print("あなたが出演できるAVは "+genrename[predict]+" 系のAVです.")
plt.show()

こうしてできたプログラムで,私が出演できそうなAVのジャンルを予測した結果がこちら.


どうやら,私の出演できそうなAVのジャンルは,野外・露出系だそうです.

野外・露出系のAVを製作しようとされているAV関係者の皆さん,当方男性ですが,ブッキングお待ちしております.

6, 参考


日本最大級のアダルトポータル FANZA

【pythonコードを読む】AV女優10000人webスクレイピング

CIFAR-100を畳み込みニューラルネットワークで認識


7, 番外編


ラベルデータを「シチュエーション」欄のタグを使用しましたが,「プレイ」欄のタグを使用することで,自分に最適なプレイを予測することもできます.私に最適なプレイはフェラだそうです.




2019年4月29日月曜日

OpenCLでFDTD法可視化 ~OpenCLとOpenGL PBOの連動~

昨年,結構新しいモデルのMacBook Proを買いました.MacBook Proは結構長い間NVidiaのGPUが入っていたので,GPUの計算は,CUDAを使ってきました.しかし,最近のモデルでは,AMDのRadeon Proが入っているので,CUDAが使えません.そこで,OpenCLを勉強し始めています.特に,映像系のプログラマとして気になるのが,OpenCLとグラフィック機能の連携.

OpenGLでテクスチャを設定するとき,一番簡単な方法としては図のようなアプローチをとります.



この方法は,GPU側で用意したテクスチャメモリに向けて,CPUのメモリのデータをコピーします.


テクスチャをいくつか用意して,それを次々と切り替えたいときは,PBO(Pixel Buffer Object)図のようなアプローチもあります.



PBOからテクスチャへのメモリコピーは,GPU内だけで高速に行えるので,テクスチャの書き換えの都合に合わせてCPUの挙動を考慮する必要がなくなります.
ただ,この方法はいずれにしてもどこかの時点でCPUからGPUへのメモリコピーを行わなければなりません.

現在のコンピュータは,PCIeの通信もそこそこ高速になってきていますので,解像度が高すぎなければ,頻繁にCPUからGPUへのメモリコピーを行なっても問題は少ないかもしれません.(私は,毎フレームglTexImage2DでCPUからGPUのテクスチャメモリへコピーして,動画をテクスチャとして表示するというような実装をよくやりますが,解像度がそんなに高くなければ全く問題ありません)

しかし,CUDAやOpenCLで何かしらの計算をして,その計算結果を濃淡画像として表示する処理をOpenGLのテクスチャを使って実現すると,図のような方法になってしまいます.



データがGPU-CPU間を1往復しています.もしこれが,CPUメモリのデータを入力として使う処理の場合(例えば,画像を色変換して表示する),更にもう一回CPUからGPUへのコピーが増えて,1.5往復することになってしまいます.多くの場合,GPUで計算する際の計算そのものより,CPUからGPUへのデータコピーの方がよほど時間がかかります.




これではせっかくのGPU計算なのに非常に効率が悪いので,こうした,GPU計算の可視化のために,例えばCUDAでは,PBOとして確保した領域をCUDAカーネル内で操作することができます.



この方法をとることで,CPUからGPUへのコピーをすることなく,テクスチャの表示を行うことができます.CPUメモリにあるデータをGPUで処理して,可視化を行う場合でも,CPU-GPU間のデータコピーは1回で済みます.




CUDAを使ったその方法は,この本に詳しくでています.

GPU 並列図形処理入門 ――CUDA・OpenGLの導入と活用

こうしたGPU計算とPBOの連動は,OpenCLでもできます.しかし,多くの資料では「できまっせ〜」みたいな感じのことが書いてあるだけで,具体的にどうやるのかをちゃんと示していません.なので,色々な情報をかき集めて,やってみました.
GPUでFDTD法を計算し,その結果を濃淡画像としてテクスチャにします.
重要なのは以下の部分.

//OpenCLの計算に使え,尚且つOpenGLテクスチャとして表示可能なメモリを確保(サイズはglBufferDataARBで設定したサイズで確保される)

d_g_data=clCreateFromGLBuffer(context, CL_MEM_READ_WRITE, pbo, &ret);



そして以下.

clEnqueueAcquireGLObjects(command_queue,1,&d_g_data,0,NULL,NULL);//テクスチャ描画可能なメモリであることを通知




全体としてはこのようになります.このソースの中で,CPUからGPUへのメモリコピーが一回も発生していないことに注目してください.

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include"glew.h"
#include<GLUT/GLUT.h>
#include<OpenCL/cl.h>
#include<OpenCL/cl_gl.h>
#include <OpenCL/cl_gl_ext.h>// for CL_CONTEXT_PROPERTY_USE_CGL_SHAREGROUP_APPLE
#include <OpenGL/CGLDevice.h> //for CGLGetShareGroup()
#include <OpenGL/CGLCurrent.h>
#define WIDTH 512
#define HEIGHT 512
#define MAX_PLATFORMS (10)
#define MAX_DEVICES (10)
void disp();
GLuint pbo;
GLuint tex;
cl_mem sf=NULL; //現在の値
cl_mem sf_1=NULL; //1回前の値
cl_mem sf_next=NULL; //次の値
cl_mem d_g_data=NULL; //テクスチャとして表示されるメモリ
cl_command_queue command_queue = NULL;
cl_kernel initval; //初期値設定カーネル
cl_kernel fdtd; //FDTD実行カーネル
cl_kernel texcopy; //計算結果をd_g_dataにコピーするカーネル
size_t local_item_size[2]={16,16};
size_t global_item_size[2]={WIDTH,HEIGHT};
int width=WIDTH,height=HEIGHT;
int main(int argc,char *argv[]){
    glutInit(&argc,argv);
    glutInitDisplayMode(GLUT_RGBA|GLUT_DOUBLE);
    glutInitWindowSize(WIDTH,HEIGHT);
    glutCreateWindow("render");
    glutDisplayFunc(disp);
    glewInit();
    glEnable(GL_TEXTURE_2D);
    //PBOを設定
    glGenBuffersARB(1,&pbo);
    glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB,pbo);
    glBufferDataARB(GL_PIXEL_UNPACK_BUFFER_ARB,WIDTH*HEIGHT*3*sizeof(unsigned char),0,GL_STREAM_DRAW_ARB);
    glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB,0);
    
    //テクスチャを設定
    glGenTextures(1,&tex);
    glBindTexture(GL_TEXTURE_2D,tex);
    glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,WIDTH,HEIGHT,0,GL_RGB,GL_UNSIGNED_BYTE,NULL);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glBindTexture(GL_TEXTURE_2D,0);
    
    //OpenCL設定
    FILE *fp;
    cl_platform_id platformid[MAX_PLATFORMS];
    cl_uint numplatforms;
    cl_uint status = 1;
    cl_uint platform = 0;
    int device = 0;
    cl_device_id     device_id[MAX_DEVICES];
    cl_uint num_devices;
    char str[BUFSIZ];
    size_t ret_size;
    cl_int ret;
    cl_context       context       = NULL;
    cl_program       program       = NULL;
    char *source_str= new char[100000];//(char *)malloc(100000 * sizeof(char));
    size_t source_size;
    clGetPlatformIDs(MAX_PLATFORMS, platformid, &numplatforms);
    clGetDeviceIDs(platformid[platform], CL_DEVICE_TYPE_ALL, MAX_DEVICES, device_id, &num_devices);
    for(device=0;device<num_devices;device++){
        clGetDeviceInfo(device_id[device], CL_DEVICE_NAME, sizeof(str), str, &ret_size);
        printf("%s %d %d\n",str,device,device_id[device]);
        if(strcmp(str,"AMD Radeon Pro 560 Compute Engine")==0) break; //"AMD Radeon Pro 560 Compute Engine"の番号のdeviceをオープンする(そのために"AMD Radeon Pro 560 Compute Engine"を見つけた時点でループを抜ける)
    }
    //Mac OSではこの部分が必要(Windowsでは不要だった)
    CGLContextObj glContext = CGLGetCurrentContext();
    CGLShareGroupObj shareGroup = CGLGetShareGroup(glContext);
    cl_context_properties props[] =
    {
        CL_CONTEXT_PROPERTY_USE_CGL_SHAREGROUP_APPLE,
        (cl_context_properties)shareGroup,
        0
    };
    //Mac OSではこの部分までが必要(Windowsでは不要だった)
    context = clCreateContext(props, 1, &device_id[device], NULL, NULL, &ret);
    command_queue = clCreateCommandQueue(context, device_id[device], 0, &ret);
    if ((fp = fopen("kernel.cl", "r")) == NULL) {
        fprintf(stderr, "kernel source open error\n");
        getchar();
        exit(1);
    }
    source_size = fread(source_str, sizeof(char), 100000, fp);
    fclose(fp);
    program = clCreateProgramWithSource(context, 1, (const char **)&source_str, (const size_t *)&source_size, &ret);
    
    //OpenCLプログラムのビルド
    clBuildProgram(program, 1, &device_id[device], NULL, NULL, NULL);
    
    //カーネルの設定
    texcopy=clCreateKernel(program, "texcopy", &ret);
    fdtd=clCreateKernel(program, "fdtd", &ret);
    initval=clCreateKernel(program, "initval", &ret);
    
    //GPU上のメモリを確保
    sf=clCreateBuffer(context, CL_MEM_READ_WRITE, WIDTH*HEIGHT*sizeof(float), NULL, &ret);
    sf_1=clCreateBuffer(context, CL_MEM_READ_WRITE, WIDTH*HEIGHT*sizeof(float), NULL, &ret);
    sf_next=clCreateBuffer(context, CL_MEM_READ_WRITE, WIDTH*HEIGHT*sizeof(float), NULL, &ret);
    
    //OpenCLの計算に使え,尚且つOpenGLテクスチャとして表示可能なメモリを確保(サイズはglBufferDataARBで設定したサイズで確保される)
    d_g_data=clCreateFromGLBuffer(context, CL_MEM_READ_WRITE, pbo, &ret);
    
    //初期化カーネル実行
    clSetKernelArg(initval, 0, sizeof(cl_mem), &sf);
    clSetKernelArg(initval, 1, sizeof(cl_mem), &sf_1);
    clSetKernelArg(initval, 2, sizeof(int), &width);
    clSetKernelArg(initval, 3, sizeof(int), &height);
    clEnqueueNDRangeKernel(command_queue, initval, 2, NULL, global_item_size, local_item_size, 0, NULL, NULL);
    
    //レンダリング開始
    glutMainLoop();
    clReleaseMemObject(sf);
    clReleaseMemObject(sf_1);
    clReleaseMemObject(sf_next);
    return 0;
}

//レンダリング関数
void disp(){
    //FDTD実行
    clSetKernelArg(fdtd, 0, sizeof(cl_mem), &sf);
    clSetKernelArg(fdtd, 1, sizeof(cl_mem), &sf_1);
    clSetKernelArg(fdtd, 2, sizeof(cl_mem), &sf_next);
    clSetKernelArg(fdtd, 3, sizeof(int), &width);
    clSetKernelArg(fdtd, 4, sizeof(int), &height);
    clEnqueueNDRangeKernel(command_queue, fdtd, 2, NULL, global_item_size, local_item_size, 0, NULL, NULL);
    
    //テクスチャ用メモリへコピー
    clEnqueueAcquireGLObjects(command_queue,1,&d_g_data,0,NULL,NULL);//テクスチャ描画可能なメモリであることを通知
    clSetKernelArg(texcopy,0,sizeof(cl_mem),&d_g_data);
    clSetKernelArg(texcopy,1,sizeof(cl_mem),&sf);
    clSetKernelArg(texcopy,2,sizeof(cl_mem),&sf_1);
    clSetKernelArg(texcopy,3,sizeof(cl_mem),&sf_next);
    clSetKernelArg(texcopy, 4, sizeof(int), &width);
    clSetKernelArg(texcopy, 5, sizeof(int), &height);
    clEnqueueNDRangeKernel(command_queue,texcopy,2,NULL,global_item_size,local_item_size,0,NULL,NULL);
    clEnqueueReleaseGLObjects(command_queue,1,&d_g_data,0,NULL,NULL);//毎回解放する必要がある
    
    //描画(CPUからGPUへのメモリコピーが発生していないことに注目)
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER,pbo);
    glBindTexture(GL_TEXTURE_2D,tex);
    glTexSubImage2D(GL_TEXTURE_2D,0,0,0,WIDTH,HEIGHT,GL_RGB,GL_UNSIGNED_BYTE,NULL);
    glBindTexture(GL_TEXTURE_2D,0);
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER,0);
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
    glBindTexture(GL_TEXTURE_2D,tex);
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
    glBegin(GL_QUADS);
    glTexCoord2f(0,0);
    glVertex3d(-1, -1, 0);
    glTexCoord2f(0,1);
    glVertex3d(-1, 1, 0);
    glTexCoord2f(1,1);
    glVertex3d(1, 1, 0);
    glTexCoord2f(1,0);
    glVertex3d(1, -1, 0);
    glEnd();
    glFlush();
    glBindTexture(GL_TEXTURE_2D,0);
    glutSwapBuffers();
    glutPostRedisplay();
}
OpenCLコード.このコードの中で,OpenCLで確保したメモリも,OpenGLで確保したPBOも同じunsigned char*で扱っていることにご注目ください.

//初期値設定

__kernel void initval(__global float *sf,__global float *sf_1,int width,int height){
    int idx=get_global_id(0);
    int idy=get_global_id(1);
    int index;
    float pi=3.14159265358979323;
    index=idx+(idy*width);
    sf[index]=0;
    sf_1[index]=0;
    if((idx>=width/2)&&(idx<width/2+40)&&(idy>=width/2)&&(idy<width/2+40)){
        sf_1[index]=0.5-0.5*cos(2*pi*(float)(idx-width/2)/40);
        sf_1[index]*=0.5-0.5*cos(2*pi*(float)(idy-width/2)/40);
        sf_1[index]*=0.73;
    }
}
//FDTD法
__kernel void fdtd(__global float *sf,__global float *sf_1,__global float *sf_next,int width,int height){
    int idx=get_global_id(0);
    int idy=get_global_id(1);
    int index=idx+(idy*width);
    int index1=idx+((idy-1)*width);
    int index2=idx+((idy+1)*width);
    int index3=(idx-1)+(idy*width);
    int index4=(idx+1)+(idy*width);
    float deltat=0.00001,deltax=0.001,c=70.0f;
    sf_next[index]=-(sf_1[index]-2*sf[index])+(c*c*deltat*deltat*(sf[index4]+sf[index3]-2*sf[index])/(deltax*deltax))+(c*c*deltat*deltat*(sf[index2]+sf[index1]-2*sf[index])/(deltax*deltax));
}
//計算結果をテクスチャ用メモリへコピー
__kernel  void texcopy(__global unsigned char *tex,__global float *sf,__global float *sf_1,__global float *sf_next,int width,int height){
    int idx=get_global_id(0);
    int idy=get_global_id(1);
    int index=idx+(idy*width);
    int tindex=index*3;
    sf_1[index]=sf[index];
    sf[index]=sf_next[index];
 
    //texはsfの3倍のメモリサイズなので(RGB画像だから)3ピクセルごと同じ値をコピーする
    tex[tindex]=(unsigned char)(sf[index]*255.0f);
    tex[tindex+1]=(unsigned char)(sf[index]*255.0f);
    tex[tindex+2]=(unsigned char)(sf[index]*255.0f);
}



OpenCLの初期化のところで,動作させるOSによって処理内容が変わるようです.本記事のコードは,MacOSX 10.13.4,Xcode 9.3でのコードです.