月別アーカイブ: 2024年12月

その他の感情分析

思考が拡散するのはリラックスしてる証拠だろうか・・・。そもそもUDPで送るのならOpenCVにこだわる必要もないし,もっと使いやすい表情分析ソリューションはないのだろうか?
Py-Feat
Python用の表情認識ツールキット。7表情やAU,特徴点,頭部の姿勢もわかる。これ・・・GPTの作成するプログラムで直ぐに動きそうに思うが。
Py-Featを使って表情認識をやってみた 基本的な使い方がわかる。
表情分析OSS Py-Featをつかってみた 頭部の姿勢検出が強力とある。

FER(Facial Expression Recognition)
これがOpenCVの元ネタに思える。

Microsoft AZURE
顔認識のサービスがある

Affectiva
かなり昔からある感情評定サービス。Unity用SDKがあるらしい。ブラウザで動作するデモが秀逸。ああ,これくらいだと利用しやすいんだけどなぁ・・・

表情模倣しながらAIと話す

プロジェクトはWindowsネイティブアプリ用に
WebGLアプリはUDPで表情認識AIの情報受け取れないし・・・一体どうしたらよいのか。結局,WebGL版の模擬人格プロジェクトをコピーして,Windowsネイティブアプリにしてビルドしてみたら,意外に良い感じで動いたので,こちらで行くことにしました。プロジェクトの名前はNative6です。ビルドターゲットのスイッチは7分弱かかって,頻繁に行うのは現実的じゃないです。
表情データの送信方式
遅きに失した感もありますが,オープンCVで認識した感情データをudpで送る部分はすでにできていたので,後はAIの情報を受け取るプログラムをGPT4oに作成してもらい,そこから感情情報を抜き出し,コミュニケーションに加味できるように,調整してみました。
実際に動かしてみて
感想としては,感情認識のタイミングがOpenAIへの問い合わせ依存なので,ややテンポが遅いようにも感じる。しかし現実のコミュニケーションも案外こんなもんかもしれない。
1.リアルタイムで感情模倣が起こることが大切なのか
2.GPT4oにこちらの感情状態が伝わることが大切なのか
どちらにしても表情を合わせてくれるので,これらの要因が実際のコミュニケーションでどんな風に機能するのかまだよくわからない。生きた人間のコミュニケーションでは,相手の(時として細やかな)表情を瞬時に読み取り,相手の意図や,コミュニケーション内容の真偽を直感的に評定していたりする。こういったコミュニケーションがAIエージェントに果たして可能なのか,そもそも必要なのか,研究はまだ始まったばかりだから分からないことだらけだ。

PsychoPy+Processing#01

PsychoPyと外部プログラムの連携
ここでは,下記のようなシンプルな実験プロジェクトに外部プログラムで測定した測定結果をUDPで送り込み保存してみる。プロジェクトは,Fixation(+)の後にTarget(□)が出るので,スペースキーを押すと,反応時間が記録されるというもの。

BuilderのPythonマーク(青と黄のヘビ)をクリックすると,Coderが起動する。ここでコードを改造すると,UnityやProcessingからのデータを受け取ることができる。ただし,Builderでコードを生成すると,改造部分はすべて消し飛ぶので注意が必要だ。

まず冒頭で,ライブラリを呼んで,グローバル変数とUDPでデータを受け取るサブルーチンを用意する。

############# NAGANO #################
import socket
import threading

# UDPソケット設定
UDP_IP = "127.0.0.1"  # 受信するIPアドレス
UDP_PORT = 5005       # ポート番号

# グローバル変数に最新のUDPデータを格納
latest_udp_data = "No Data"

def udp_listener():
    """非同期でUDPデータを受信する関数"""
    global latest_udp_data
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind((UDP_IP, UDP_PORT))
    while True:
        data, addr = sock.recvfrom(1024)  # 1024バイトまで受信
        latest_udp_data = data.decode("utf-8")  # デコードして文字列に
############# NAGANO #################

次にプログラム開始部分でスレッドをスタートする。

    win = setupWindow(expInfo=expInfo)
    setupDevices(expInfo=expInfo, thisExp=thisExp, win=win)
    
    ############# NAGANO #################
    # UDPリスナーをスレッドで起動
    udp_thread = threading.Thread(target=udp_listener, daemon=True)
    udp_thread.start()    
    ############# NAGANO #################

でさらに,UDPでうけとったデータを保存する。データの受取は,別スレッドが非同期で行ってくれる。

                    key_resp_target.duration = _key_resp_target_allKeys[-1].duration
                    
                    ############# NAGANO #################
                    # UDPデータをログに記録
                    thisExp.addData('UDP_Data', latest_udp_data)
                    thisExp.addData('KeyPress_Time', globalClock.getTime())  # キー押下時刻も記録
                    ############# NAGANO #################
        
                    # a response ends the routine
                    continueRoutine = False

実行すると,以下のような感じでUDP経由で送られたデータが保存される。ちなみに,データ送信側は,ローカルホストの5005ポートにProcessingでCerry,Diamond,Sevenのどれかひとつの文字列を送るもの。もう一度いうが,Builderで更新するとこれらの改造部分は消し飛ぶので注意

SpeakerID一覧

VoiceVOXやCoeiroInkのSpeakerIDが必要になるときがある。・・・あるんだってば。VoiceVOXに関してはここから参照。

キャラクター名 スタイル ID
四国めたん ノーマル 2
あまあま 0
ツンツン 6
セクシー 4
ささやき 36
ヒソヒソ 37
ずんだもん ノーマル 3
あまあま 1
ツンツン 7
セクシー 5
ささやき 22
ヒソヒソ 38
春日部つむぎ ノーマル 8
雨晴はう ノーマル 10
波音リツ ノーマル 9
玄野武宏 ノーマル 11
喜び 39
ツンギレ 40
悲しみ 41
白上虎太郎 ふつう 12
わーい 32
びくびく 33
おこ 34
びえーん 35
青山龍星 ノーマル 13
冥鳴ひまり ノーマル 14
九州そら ノーマル 16
あまあま 15
ツンツン 18
セクシー 17
ささやき 19
もち子さん ノーマル 20
剣崎雌雄 ノーマル 21
WhiteCUL ノーマル 23
たのしい 24
かなしい 25
びえーん 26
後鬼 人間ver. 27
ぬいぐるみver. 28
No.7 ノーマル 29
アナウンス 30
読み聞かせ 31
ちび式じい ノーマル 42
櫻歌ミコ ノーマル 43
第二形態 44
ロリ 45
小夜/SAYO ノーマル 46
ナースロボ_タイプT ノーマル 47
楽々 48
恐怖 49
内緒話 50

CoeiroINKは下記のような感じ
//つくよみちゃん(れいせい) 0
//つくよみちゃん(おしとやか) 5
//つくよみちゃん(元気) 6
//KANA(のーまる) 30
//KANA(えんげき) 31
//KANA(ほうかご) 32
//リリンちゃん(ノーマル) 90
//リリンちゃん(ささやき) 91
//モモイヒナ-A 1737595468
//KAKU_N 259
//KAKU_W 260

AI教材2024-1

Python編
Anacondaをインストールし、環境をつくるところまでは一緒。新しいライブラリ(1.0.0以降)に対応したコードがなかなか見つからない。今回はここを参考にした。Pythonのバージョンは3.10.15、OPENAIライブラリのバージョンは1.57.0。
追加予定コンテンツ:GPT4Vによる画像解析、Voicevoxとの連携

GPT01/最低限のコード
文章作成を行うための最低限のコード。Temperatureが指定できる。

#GPT01.py
#GPT4を使って文章生成を行う最低限のコード
from openai import OpenAI

client = OpenAI(api_key="sk-hogehoge")

completion = client.chat.completions.create(
    model="gpt-4o",
    messages=[
        {"role": "system", "content": "あなたは大学図書館の優秀な受付です"},
        {"role": "user", "content": "初めて図書館に来た大学生に、図書館の魅力を200文字で伝えてください。"}
    ],
    #temperature=0.1文章が一貫性の高い回答
    #temperature=0.9より創造的で予想外な回答
    temperature=0.7
)

# 日本語の文字列部分だけを取得
response_text = completion.choices[0].message.content
print(response_text)

GPT02/使用したトークンの取得
文章生成に使用したトークンの数を表示してくれる

#GPT02.py
#GPT4を使って文章生成を行い消費したトークンを表示
from openai import OpenAI

client = OpenAI(api_key="sk-hogehoge")

completion = client.chat.completions.create(
    model="gpt-4o",
    messages=[
        {"role": "system", "content": "あなたは大学図書館の優秀な受付です"},
        {"role": "user", "content": "初めて図書館に来た大学生に、図書館の魅力を200文字で伝えてください。"}
    ],
    temperature=0.7
)

# 日本語の文字列部分だけを取得
response_text = completion.choices[0].message.content
print(response_text)

# 消費したトークン数を表示
total_tokens = completion.usage.total_tokens
prompt_tokens = completion.usage.prompt_tokens
completion_tokens = completion.usage.completion_tokens

print(f"消費したトークン数: 合計 {total_tokens}, プロンプト {prompt_tokens}, 応答 {completion_tokens}")

Chat01/チャット用スクリプト
人格を設定して会話を行うことができる。

#Chat01.py
#設定した人格に基づいて、チャットを行うスクリプト。
from openai import OpenAI

client = OpenAI(api_key="sk-hogehoge")

personality = "あなたは、埼玉県内の大学に通う男子大学2年生です。所属学科は心理学科です。一人称は「僕」、二人称は「きみ」を使います。明るく気さくな感じで話します。「〇〇だよな」「〇〇だな」「〇〇だろ?」「〇〇だと思う」などの語尾を使って話します。"

# 初期設定
messages = [
    {"role": "system", "content": personality}
]

print("模擬人格と会話を始めます。'終了'と入力すると終了します。")

while True:
    # ユーザーの入力を受け取る
    user_input = input("user: ")
    
    # 終了コマンド
    if user_input.strip() == "終了":
        print("会話を終了します。")
        break

    # ユーザー発言を保存
    messages.append({"role": "user", "content": user_input})
    
    # 会話履歴を最新20件に制限
    if len(messages) > 21:  # 21 = systemメッセージ + 過去20回の履歴
        messages.pop(1)  # 最初のユーザーかアシスタントの履歴を削除
    
    # OpenAI APIにリクエスト
    completion = client.chat.completions.create(  # 修正箇所
        model="gpt-4o",
        messages=messages,
        temperature=0.7
    )
    
    # アシスタントの応答を取得
    assistant_response = completion.choices[0].message.content
    print(f"assistant: {assistant_response}")
    
    # アシスタントの応答を保存
    messages.append({"role": "assistant", "content": assistant_response})

VVOX01/合成音声を作成する最低限のスクリプト
ローカルで実行中のvoicevoxにむかってテキストを送り込み、合成音声として再生してもらいます。voicevoxのspeakerIDはこちらを参照。

#VVOX01.py
#ローカルで起動したvoicevoxに、pythonから文字を送って合成音声を再生するスクリプト。
import requests
import json
from pydub import AudioSegment, playback
 
HOSTNAME='http://localhost:50021'
speaker     = 3 #ずんだもん
msg="こんにちは、ずんだもんです!"
 
def playsound(text):
    # audio_query (音声合成用のクエリを作成するAPI)
    res1 = requests.post(HOSTNAME + '/audio_query',
                        params={'text': text, 'speaker': speaker})
 
    # synthesis (音声合成するAPI)
    res2 = requests.post(HOSTNAME + '/synthesis',
                        params={'speaker': speaker},
                        data=json.dumps(res1.json()))
 
    # wavの音声を再生
    playback.play(AudioSegment(res2.content,
        sample_width=2, frame_rate=22050, channels=1))
 
playsound(msg)

Chat02/合成音声でAIとチャットを行うスクリプト
チャットスクリプトと合成音声スクリプトを統合したもの

#Chat02.py
#設定した人格に基づいて、チャットを行い、AIの返答を合成音声で返答するスクリプト。

from openai import OpenAI
import requests
import json
from pydub import AudioSegment, playback

# OpenAI API設定
client = OpenAI(api_key="sk-hogehoge")

# VoiceVox設定
HOSTNAME = 'http://localhost:50021'
speaker = 13  # 青山龍星

def playsound(text):
    """VoiceVoxで音声合成して再生する"""
    # audio_query (音声合成用のクエリを作成するAPI)
    res1 = requests.post(HOSTNAME + '/audio_query',
                         params={'text': text, 'speaker': speaker})
    
    # synthesis (音声合成するAPI)
    res2 = requests.post(HOSTNAME + '/synthesis',
                         params={'speaker': speaker},
                         data=json.dumps(res1.json()))
    
    # wavの音声を再生
    playback.play(AudioSegment(res2.content,
                               sample_width=2, frame_rate=24000, channels=1))

# AIの人格設定
personality = "あなたは、アイオワ州在住の陸軍兵士です。一人称は「おれ」、二人称は「おまえ」を使います。「〇〇だよな」「〇〇だな」「〇〇だろ?」「〇〇だと思う」などの語尾を使って話します。ぶっきらぼうだけど、本当は優しい性格で、ユーザーの会話を掘り下げ、悩み相談にのってくれる。"

# 初期設定
messages = [
    {"role": "system", "content": personality}
]

print("模擬人格と会話を始めます。'終了'と入力すると終了します。")

while True:
    # ユーザーの入力を受け取る
    user_input = input("user: ")
    
    # 終了コマンド
    if user_input.strip() == "終了":
        print("会話を終了します。")
        break

    # ユーザー発言を保存
    messages.append({"role": "user", "content": user_input})
    
    # 会話履歴を最新20件に制限
    if len(messages) > 21:
        messages.pop(1)
    
    # OpenAI APIにリクエスト
    completion = client.chat.completions.create(
        model="gpt-4o",
        messages=messages,
        temperature=0.7
    )
    
    # アシスタントの応答を取得
    assistant_response = completion.choices[0].message.content
    print(f"assistant: {assistant_response}")
    
    # アシスタントの応答を保存
    messages.append({"role": "assistant", "content": assistant_response})
    
    # VoiceVoxで音声合成&再生
    playsound(assistant_response)

GPT4V/画像を読み込み解釈する
指定したファイル名の画像を、プロンプトに従って解釈し、説明します。

#GPT4V.py
#画像を読み込んで、説明を行うスクリプト。
import base64
from pathlib import Path
from openai import OpenAI

client = OpenAI(api_key="sk-hogehoge") 
 
def encode_image(image_path):    
    with open(image_path, "rb") as image_file:
        return base64.b64encode(image_file.read()).decode('utf-8')
 
image_path = "picture1.jpg"
base64_image = encode_image(image_path)
file_extension = Path(image_path).suffix
file_extension_without_dot = file_extension[1:]
 
url = f"data:image/{file_extension_without_dot};base64,{base64_image}"
 
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": "どのような状況か300文字くらいで説明してください。季節も推測してください。",
                },
                {
                    "type": "image_url",
                    "image_url": {
                        "url": url,
                    },
                },
            ],
        }
    ],
    max_tokens=1000,
)
 
print(response.choices[0].message.content)