指文字3次元ビューワ(ソースコードと説明と利用ガイド)
【概要】
日本語指文字(手指で五十音を表す方法)46文字の手形を、21点の3次元ランドマークとして保持し、3次元表示するプログラムである。点の並びは MediaPipe Hand Landmarker の規約(0:手首、1-4:親指、5-8:人差し指、9-12:中指、13-16:薬指、17-20:小指)に従う。マウスのドラッグで視点を回転し、ホイールで拡大縮小できる。指ごとに色を変え、奥にあるものほど色を白に近づけて奥行きを表す。Python標準ライブラリのtkinterだけで動作し、追加パッケージのインストールは不要である。
【目次】
第1章 事前準備
ここでは、最低限の事前準備について説明する。機械学習や深層学習を行う場合は、NVIDIA CUDA、Visual Studio、Cursorなどを追加でインストールすると便利である。これらについては別ページ https://www.kkaneko.jp/cc/dev/aiassist.html で説明しているので、必要に応じて参照する。
本プログラムは、Python標準ライブラリのtkinterとmathだけを使用するため、追加パッケージのインストールは不要である。
第2章 Python 3.12 のインストール
Pythonのインストールを行い、Pythonのプログラムを実行する環境を整える。扱う環境は、Windows搭載パソコンである。金子研究室では、Python 3.12.10を推奨する。
[Windows での Python 3.12 のインストール手順を見るには、ここをクリック]
Windows での Python 3.12 のインストール
以下のいずれかの方法でPython 3.12をインストールする。Pythonがインストール済みの場合、この手順は不要である。
方法 1:winget によるインストール
【インストールコマンドの実行方法】
管理者権限でコマンドプロンプトを起動する(手順:Windowsキーまたはスタートメニュー → cmd と入力 → 右クリック → 「管理者として実行」)。そして、コマンド全体をコマンドプロンプトにコピー&ペーストする。
--scope machine を指定することで、システム全体(全ユーザー向け)にインストールされる。このオプションの実行には管理者権限が必要である。インストール完了後、コマンドプロンプトを再起動するとPATHが反映される。
REM Python 3.12 をシステム領域にインストール
winget install --id Python.Python.3.12 -e --scope machine --silent --accept-source-agreements --accept-package-agreements --override "/quiet InstallAllUsers=1 PrependPath=1 Include_test=0 Include_pip=1 Include_launcher=1 InstallLauncherAllUsers=1 TargetDir=\"C:\Program Files\Python312\""
if not "%ERRORLEVEL%"=="0" ( color 0c & echo Python 3.12 のインストールに失敗しました & ping 127.0.0.1 -n 6 >nul & color )
REM Python と Scripts を PATH 先頭に追加
powershell -NoProfile -Command "$p='C:\Program Files\Python312'; $s=\"$p\Scripts\"; if(Test-Path $p){$k=[Microsoft.Win32.Registry]::LocalMachine.OpenSubKey('SYSTEM\CurrentControlSet\Control\Session Manager\Environment',$true); $c=$k.GetValue('Path','',[Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames); $t=$k.GetValueKind('Path'); $new=$c; if((';'+$new+';') -notlike \"*;$p;*\"){$new=$p+';'+$new}; if((';'+$new+';') -notlike \"*;$s;*\"){$new=$s+';'+$new}; if($new -ne $c){$k.SetValue('Path',$new,$t)}; $k.Close()}"
REM 現在のセッションにも反映(システムPATHを再取得して連結)
for /f "usebackq tokens=2,*" %A in (`reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v Path`) do set "PATH=%B"
REM pip / wheel の更新
python -m pip install --no-user -U pip wheel
if not "%ERRORLEVEL%"=="0" ( color 0c & echo pip / wheel の更新に失敗しました & ping 127.0.0.1 -n 6 >nul & color )
方法 2:インストーラーによるインストール
- Python公式サイト(https://www.python.org/downloads/)にアクセスし、「Download Python 3.x.x」ボタンからWindows用インストーラーをダウンロードする。
- ダウンロードしたインストーラーを実行する。
- 初期画面の下部に表示される「Add python.exe to PATH」にチェックを入れてから「Customize installation」を選択する。このチェックを入れ忘れると、コマンドプロンプトから
pythonコマンドを実行できない。 - 「Install Python 3.xx for all users」にチェックを入れ、「Install」をクリックする。
インストールの確認
コマンドプロンプトで以下を実行する。
python --version
バージョン番号(例:Python 3.12.x)が表示されればインストール成功である。「'python' は、内部コマンドまたは外部コマンドとして認識されていません。」と表示される場合は、インストールが正常に完了していない。
第3章 Python の開発環境 Visual Studio Code のインストールと Python 用の設定
Python の開発環境Visual Studio Code(プログラムを編集するソフトウェア。以下、VS Code)を整える。
[Windows での Visual Studio Code のインストールと Python 用の設定手順を見るには、ここをクリック]
Windows での Visual Studio Code のインストールと Python 用の設定手順
1. VS Code と拡張機能のインストール
以下のコマンドにより,既存の VS Code を削除し,全ユーザー共有の設定で再インストールしたうえで,拡張機能(VS Code に機能を追加するソフトウェア)をまとめて導入する.
【インストールコマンドの実行方法】
管理者権限でコマンドプロンプトを起動する(手順:Windows キーまたはスタートメニュー → cmd と入力 → 右クリック → 「管理者として実行」)。そして,コマンド全体をコマンドプロンプトにコピー&ペーストする。
インストールコマンド
REM ============================================================
REM Microsoft Visual Studio Code
REM ============================================================
REM Build Tools + Desktop development with C++(VCTools)+ 追加コンポーネント(一括)
REM 未インストール時: winget で新規インストール
REM インストール済み時: setup.exe modify でコンポーネント追加(バージョンは変更しない)
winget list --id Microsoft.VisualStudio.BuildTools 2>nul | findstr /i "BuildTools" >nul 2>&1
if %ERRORLEVEL% EQU 0 (
for /f "usebackq delims=" %P in (`"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -products Microsoft.VisualStudio.Product.BuildTools -property installationPath`) do start /wait "" "C:\Program Files (x86)\Microsoft Visual Studio\Installer\setup.exe" modify --installPath "%P" --add Microsoft.VisualStudio.Workload.VCTools --add Microsoft.VisualStudio.Workload.MSBuildTools --add Microsoft.VisualStudio.Component.VC.CMake.Project --add Microsoft.VisualStudio.Component.VC.Llvm.Clang --add Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset --add Microsoft.VisualStudio.Component.Windows11SDK.26100 --add Microsoft.VisualStudio.Component.VC.v143.x86.x64 --includeRecommended --quiet --norestart --nocache
if not "%ERRORLEVEL%"=="0" ( color 0c & echo Build Tools のコンポーネント追加に失敗しました & ping 127.0.0.1 -n 6 >nul & color )
) else (
winget install --scope machine --id Microsoft.VisualStudio.BuildTools -e --silent --disable-interactivity --force --accept-source-agreements --accept-package-agreements --override "--quiet --wait --norestart --nocache --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended --add Microsoft.VisualStudio.Workload.MSBuildTools --add Microsoft.VisualStudio.Component.VC.CMake.Project --add Microsoft.VisualStudio.Component.VC.Llvm.Clang --add Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset --add Microsoft.VisualStudio.Component.Windows11SDK.26100 --add Microsoft.VisualStudio.Component.VC.v143.x86.x64"
if not "%ERRORLEVEL%"=="0" ( color 0c & echo Build Tools のインストールに失敗しました & ping 127.0.0.1 -n 6 >nul & color )
)
REM 全ユーザー共有の拡張機能フォルダ
if not exist "C:\ProgramData\vscode-extensions" mkdir "C:\ProgramData\vscode-extensions"
icacls "C:\ProgramData\vscode-extensions" /grant "Everyone:(OI)(CI)M" /T
REM スタートメニューのショートカットを --extensions-dir 付きで再作成
if exist "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code" rmdir /s /q "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code"
if exist "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code.lnk" del "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code.lnk"
powershell -NoProfile -Command "$s=New-Object -ComObject WScript.Shell; $lnk=$s.CreateShortcut('C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code.lnk'); $lnk.TargetPath='C:\Program Files\Microsoft VS Code\Code.exe'; $lnk.Arguments='--extensions-dir \"C:\ProgramData\vscode-extensions\"'; $lnk.Save()"
REM ショートカットの検証
powershell -NoProfile -Command "$s=New-Object -ComObject WScript.Shell; $lnk=$s.CreateShortcut('C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code.lnk'); Write-Host 'TargetPath:' $lnk.TargetPath; Write-Host 'Arguments:' $lnk.Arguments"
REM ファイル / フォルダ右クリックの「Code で開く」を登録
reg add "HKLM\SOFTWARE\Classes\*\shell\VSCode\command" /ve /d "\"C:\Program Files\Microsoft VS Code\Code.exe\" --extensions-dir \"C:\ProgramData\vscode-extensions\" \"%1\"" /f
reg add "HKLM\SOFTWARE\Classes\Directory\shell\VSCode\command" /ve /d "\"C:\Program Files\Microsoft VS Code\Code.exe\" --extensions-dir \"C:\ProgramData\vscode-extensions\" \"%1\"" /f
reg add "HKLM\SOFTWARE\Classes\Directory\Background\shell\VSCode\command" /ve /d "\"C:\Program Files\Microsoft VS Code\Code.exe\" --extensions-dir \"C:\ProgramData\vscode-extensions\" \"%V\"" /f
REM --extensions-dir 付きで起動する code.cmd ラッパを作成
REM (%* を echo で書くと対話的 cmd で失われるため、PowerShell で [char]37+'*' を書き出す)
powershell -NoProfile -Command "$pct=[char]37; $q=[char]34; $c='@echo off'+[char]13+[char]10+$q+'C:\Program Files\Microsoft VS Code\bin\code.cmd'+$q+' --extensions-dir '+$q+'C:\ProgramData\vscode-extensions'+$q+' '+$pct+'*'+[char]13+[char]10; [IO.File]::WriteAllText('C:\ProgramData\vscode-extensions\vscode.cmd',$c,[Text.Encoding]::ASCII)"
REM 拡張機能のインストール
set "CODE=C:\Program Files\Microsoft VS Code\bin\code.cmd"
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --uninstall-extension GitHub.copilot
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --uninstall-extension GitHub.copilot-chat
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension ms-python.python
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension ms-python.vscode-pylance
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension ms-python.debugpy
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension MS-CEINTL.vscode-language-pack-ja
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension saoudrizwan.claude-dev
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension rust-lang.rust-analyzer
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension tamasfe.even-better-toml
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension anthropic.claude-code
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension almenon.arepl
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --list-extensions --show-versions
REM settings.json を作成(自動更新オフ、Python、Claude Code 設定)
if not exist "%APPDATA%\Code\User" mkdir "%APPDATA%\Code\User"
python -c "import json,os;data={'update.mode':'none','update.enableWindowsBackgroundUpdates':False,'extensions.autoUpdate':False,'python.defaultInterpreterPath':r'C:\Program Files\Python312\python.exe','claudeCode.environmentVariables':[{'name':'ANTHROPIC_API_KEY','value':'not-needed'},{'name':'ANTHROPIC_AUTH_TOKEN','value':'ollama'},{'name':'ANTHROPIC_BASE_URL','value':'http://localhost:11434'},{'name':'ANTHROPIC_MODEL','value':'glm-4.7-flash'},{'name':'CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC','value':'1'}]};p=os.path.join(os.environ['APPDATA'],'Code','User','settings.json');open(p,'w',encoding='utf-8').write(json.dumps(data,indent=4));print('Done:',p)"
REM 自動更新の抑止ポリシー(settings.json に加えて、レジストリ側でも明示的にオフ)
reg add "HKLM\SOFTWARE\Policies\Microsoft\VSCode" /v "UpdateMode" /t REG_SZ /d "none" /f
echo === セットアップ完了 ===
2. Python インタプリタの選択
同一マシンに複数の Python がインストールされている場合,VS Code で使用する Python 本体(インタプリタ:Python プログラムを解釈・実行するソフトウェア)を選択する必要がある.
- コマンドパレット(コマンド名で機能を呼び出す VS Code の入力欄)を開く(
Ctrl+Shift+P) Python: Select Interpreterと入力する
- 表示される一覧から,使用する Python(例:
C:\Program Files\Python312\python.exe)を選択する.
第4章 指文字3次元ビューワのプログラム
概要
本プログラムは、日本語指文字46文字それぞれの手形を3次元で表示する。画面左のボタンで文字を選ぶと、その文字の手形が右側のキャンバスに描画され、文字の由来と指の状態(伸ばす/曲げる)が表示される。手形のデータはプログラム内に定数として持つため、カメラや学習済みモデルを使わずに動作する。
主要技術
21点ハンドランドマーク
MediaPipe Hand Landmarker は、手を21個の点で表す[1]。点の番号は、0が手首、1から4が親指、5から8が人差し指、9から12が中指、13から16が薬指、17から20が小指であり、各指は付け根から指先へ向かって番号が増える。本プログラムの手形データと骨格の接続は、この規約に従う。
透視投影
3次元座標を2次元の画面に描くために、奥にあるものほど小さく描く変換を行う。本プログラムでは、視点からの距離に応じた倍率を座標に掛けることで実現する。
画家のアルゴリズム
奥にあるものから順に描き、手前のもので上書きすることで前後関係を表す描画方法である。本プログラムは、線と点をまとめて奥行き順に並べ替えてから描画する。
技術的特徴
-
指ごとの色分け
親指を赤、人差し指を緑、中指を青、薬指を黄、小指をマゼンタ、手のひらをグレーで描き、どの指がどの形になっているかを見分けやすくする。
-
奥行きの表現
奥にある点や線ほど色を白に近づけ、あわせて奥にあるものから先に描くことで、手前と奥の区別を付ける。ランドマークのZ座標は、値が大きいほど奥(視点から遠い)を表す。
-
重心を原点とした回転
描画の前に手形の重心を求めて原点へ移動するため、どの文字を選んでも手形が画面の中央付近に表示され、回転の中心もずれない。
-
標準ライブラリのみで動作
tkinterとmathだけを使用するため、追加パッケージのインストールを必要としない。
操作方法
- 文字の選択: 画面左のボタンをクリックすると、その文字の手形に切り替わる。ボタンはあ行からわ行まで並んでおり、一覧はスクロールできる。
- 回転: キャンバス上でマウスの左ボタンを押しながら動かすと、上下のドラッグでX軸まわり、左右のドラッグでY軸まわりに視点が回る。
- 拡大縮小: キャンバス上でマウスホイールを回すと表示倍率が変わる。
- 終了: ウィンドウを閉じるとプログラムが終了する。
参考文献
- [1] Google. Hand landmarks detection guide. https://ai.google.dev/edge/mediapipe/solutions/vision/hand_landmarker
ソースコード
"""
プログラム名: 日本語指文字 3次元ビューワ
特徴技術名: MediaPipe Hand Landmarker の21点ハンドランドマーク規約
出典: Google. Hand landmarks detection guide. https://ai.google.dev/edge/mediapipe/solutions/vision/hand_landmarker
特徴機能: 手を21点(0:手首、1-4:親指、5-8:人差し指、9-12:中指、13-16:薬指、17-20:小指)で表す規約に従い、
日本語指文字46文字の手形を3次元座標として保持し、透視投影で描画する
学習済みモデル: 使用しない(手形データはプログラム内に定数として保持)
方式設計:
関連利用技術:
- tkinter: ウィンドウとCanvasによる描画、マウス操作の受け取り(Python標準ライブラリ)
- math: 回転行列の計算に用いる三角関数(Python標準ライブラリ)
入力と出力: 入力: 画面左の文字選択ボタン、キャンバス上のマウス操作(ドラッグで回転、ホイールで拡大縮小)、
出力: キャンバスへの3次元手形の描画と、文字の説明・指の状態の表示
処理手順: 1.文字選択→2.手形の21点座標を取得→3.重心を原点へ移動→4.X軸・Y軸まわりに回転→
5.透視投影で2次元座標へ変換→6.奥のものから順に線と点を描画
前処理、後処理: 前処理:重心を原点へ移動、後処理:奥行きに応じて色を白に近づけ、奥のものから描画
追加処理: 指ごとの色分け表示
調整を必要とする設定値: zoom(表示倍率、初期値1500)、rotation_x/rotation_y(初期の視点角度)
将来方策: 手形データの精度向上 - 実際の手をMediaPipe Hand Landmarkerで計測した座標に置き換える
その他の重要事項: 追加パッケージのインストールは不要(Python標準ライブラリのみで動作)
前準備: なし
"""
import tkinter as tk
import math
# ----------------------------------------------------------------------
# 全文字のランドマークデータ
# ----------------------------------------------------------------------
# 21点の [X, Y, Z] (単位はメートル相当)。Zは値が大きいほど奥(カメラから遠い)。
LANDMARKS_DATA = {
# ランドマークの構造: [X, Y, Z]
'あ': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.07,0.035,-0.005],[0.085,0.065,0.005],[0.08,0.085,0.01],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
'い': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.08,0.0],[0.0,0.085,0.01],[0.0,0.09,0.01]],
'う': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.09,0.0],[0.025,0.13,0.0],[0.025,0.16,0.0],[0.0,0.05,0.0],[0.0,0.09,0.0],[0.0,0.13,0.0],[0.0,0.16,0.0],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
'え': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.02],[0.025,0.08,0.025],[0.025,0.09,0.03],[0.0,0.05,0.0],[0.0,0.07,0.02],[0.0,0.08,0.025],[0.0,0.09,0.03],[-0.025,0.05,0.0],[-0.025,0.07,0.02],[0.0,0.08,0.025],[0.0,0.09,0.03],[-0.045,0.045,0.0],[-0.045,0.065,0.02],[0.0,0.08,0.025],[0.0,0.09,0.03]],
'お': [[0.0,0.0,0.0],[0.02,0.015,-0.015],[0.04,0.03,-0.005],[0.04,0.045,0.0],[0.03,0.055,0.015],[0.01,0.05,0.0],[0.01,0.07,0.005],[0.01,0.08,0.01],[0.01,0.09,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.08,0.01],[0.0,0.09,0.015],[-0.01,0.05,0.0],[-0.01,0.07,0.005],[-0.01,0.08,0.01],[-0.01,0.09,0.015],[-0.025,0.045,0.0],[-0.025,0.065,0.005],[-0.025,0.08,0.01],[-0.025,0.095,0.015]],
'か': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.09,0.0],[0.025,0.13,0.0],[0.025,0.16,0.0],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
'き': [[0.0,0.0,0.0],[0.05,0.01,-0.01],[0.08,0.025,0.0],[0.1,0.04,0.02],[0.11,0.05,0.04],[0.04,0.05,0.0],[0.04,0.09,0.0],[0.04,0.13,0.0],[0.04,0.16,0.0],[0.0,0.05,0.0],[0.0,0.07,0.02],[0.0,0.09,0.03],[0.0,0.1,0.04],[-0.04,0.05,0.0],[-0.04,0.07,0.02],[-0.04,0.09,0.03],[-0.04,0.1,0.04],[-0.07,0.045,0.0],[-0.07,0.08,0.0],[0.0,0.095,0.0],[0.0,0.1,0.0]],
'く': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.07,0.035,-0.005],[0.085,0.065,0.005],[0.07,0.095,0.01],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
'け': [[0.0,0.0,0.0],[0.02,0.015,-0.015],[0.035,0.035,-0.01],[0.05,0.05,0.0],[0.06,0.06,0.0],[0.025,0.05,0.0],[0.025,0.09,0.0],[0.025,0.13,0.0],[0.025,0.16,0.0],[0.0,0.05,0.0],[0.0,0.09,0.0],[0.0,0.13,0.0],[0.0,0.16,0.0],[-0.025,0.05,0.0],[-0.025,0.09,0.0],[-0.025,0.13,0.0],[-0.025,0.16,0.0],[-0.045,0.045,0.0],[-0.045,0.085,0.0],[0.0,0.11,0.0],[0.0,0.12,0.0]],
'こ': [[0.0,0.0,0.0],[0.04,0.015,-0.015],[0.06,0.035,-0.01],[0.07,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.05,0.0],[0.025,0.05,0.0],[0.025,0.05,0.0],[0.0,0.05,0.0],[0.0,0.05,0.0],[0.0,0.05,0.0],[0.0,0.05,0.0],[-0.025,0.05,0.0],[-0.025,0.05,0.0],[-0.025,0.05,0.0],[-0.025,0.05,0.0],[-0.045,0.045,0.0],[-0.045,0.045,0.0],[0.0,0.045,0.0],[0.0,0.045,0.0]],
'さ': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.07,0.035,-0.005],[0.085,0.065,0.005],[0.09,0.085,0.01],[0.025,0.05,0.0],[0.025,0.09,0.0],[0.025,0.13,0.0],[0.025,0.16,0.0],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
'し': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.09,0.0],[0.025,0.13,0.0],[0.025,0.16,0.0],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
'す': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.02],[0.025,0.08,0.025],[0.025,0.09,0.03],[0.0,0.05,0.0],[0.0,0.07,0.02],[0.0,0.08,0.025],[0.0,0.09,0.03],[-0.025,0.05,0.0],[-0.025,0.07,0.02],[0.0,0.08,0.025],[0.0,0.09,0.03],[-0.045,0.045,0.0],[-0.045,0.065,0.02],[0.0,0.08,0.025],[0.0,0.09,0.03]],
'せ': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.07,0.035,-0.005],[0.085,0.065,0.005],[0.09,0.085,0.01],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.09,0.0],[0.0,0.13,0.0],[0.0,0.16,0.0],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
'そ': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.02],[0.025,0.08,0.025],[0.025,0.09,0.03],[0.0,0.05,0.0],[0.0,0.09,0.0],[0.0,0.13,0.0],[0.0,0.16,0.0],[-0.025,0.05,0.0],[-0.025,0.09,0.0],[-0.025,0.13,0.0],[-0.025,0.16,0.0],[-0.045,0.045,0.0],[-0.045,0.085,0.0],[0.0,0.11,0.0],[0.0,0.12,0.0]],
'た': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
'ち': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.07,0.035,-0.005],[0.085,0.065,0.005],[0.09,0.085,0.01],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
'つ': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.07,0.035,-0.005],[0.085,0.065,0.005],[0.09,0.085,0.01],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
'て': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.09,0.0],[0.025,0.13,0.0],[0.025,0.16,0.0],[0.0,0.05,0.0],[0.0,0.09,0.0],[0.0,0.13,0.0],[0.0,0.16,0.0],[-0.025,0.05,0.0],[-0.025,0.09,0.0],[-0.025,0.13,0.0],[-0.025,0.16,0.0],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
'と': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
'な': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.07,0.035,-0.005],[0.085,0.065,0.005],[0.09,0.085,0.01],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
'に': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.09,0.0],[0.025,0.13,0.0],[0.025,0.16,0.0],[0.0,0.05,0.0],[0.0,0.09,0.0],[0.0,0.13,0.0],[0.0,0.16,0.0],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
'ぬ': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.07,0.035,-0.005],[0.085,0.065,0.005],[0.09,0.085,0.01],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
'ね': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.07,0.035,-0.005],[0.085,0.065,0.005],[0.09,0.085,0.01],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
'の': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.07,0.035,-0.005],[0.085,0.065,0.005],[0.09,0.085,0.01],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.09,0.0],[0.0,0.13,0.0],[0.0,0.16,0.0],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
'は': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.07,0.035,-0.005],[0.085,0.065,0.005],[0.09,0.085,0.01],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
'ひ': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.09,0.0],[0.025,0.13,0.0],[0.025,0.16,0.0],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
'ふ': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.09,0.0],[0.025,0.13,0.0],[0.025,0.16,0.0],[0.0,0.05,0.0],[0.0,0.09,0.0],[0.0,0.13,0.0],[0.0,0.16,0.0],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
'へ': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.07,0.035,-0.005],[0.085,0.065,0.005],[0.09,0.085,0.01],[0.025,0.05,0.0],[0.025,0.09,0.0],[0.025,0.13,0.0],[0.025,0.16,0.0],[0.0,0.05,0.0],[0.0,0.09,0.0],[0.0,0.13,0.0],[0.0,0.16,0.0],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
'ほ': [[0.0,0.0,0.0],[0.02,0.015,-0.015],[0.035,0.035,-0.01],[0.05,0.05,0.0],[0.06,0.06,0.0],[0.025,0.05,0.0],[0.025,0.09,0.0],[0.025,0.13,0.0],[0.025,0.16,0.0],[0.0,0.05,0.0],[0.0,0.09,0.0],[0.0,0.13,0.0],[0.0,0.16,0.0],[-0.025,0.05,0.0],[-0.025,0.09,0.0],[-0.025,0.13,0.0],[-0.025,0.16,0.0],[-0.045,0.045,0.0],[-0.045,0.085,0.0],[0.0,0.11,0.0],[0.0,0.12,0.0]],
'ま': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.07,0.035,-0.005],[0.085,0.065,0.005],[0.09,0.085,0.01],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
'み': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
'む': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
'め': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.09,0.0],[0.0,0.13,0.0],[0.0,0.16,0.0],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
'も': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.09,0.0],[0.0,0.13,0.0],[0.0,0.16,0.0],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
'や': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.09,0.0],[0.0,0.13,0.0],[0.0,0.16,0.0]],
'ゆ': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.07,0.035,-0.005],[0.085,0.065,0.005],[0.09,0.085,0.01],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
'よ': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
'ら': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
'り': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.09,0.0],[0.025,0.13,0.0],[0.025,0.16,0.0],[0.0,0.05,0.0],[0.0,0.09,0.0],[0.0,0.13,0.0],[0.0,0.16,0.0],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
'る': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
'れ': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.09,0.0],[0.0,0.13,0.0],[0.0,0.16,0.0],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
'ろ': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
'わ': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
'を': [[0.0,0.0,0.0],[0.02,0.015,-0.015],[0.035,0.035,-0.01],[0.05,0.05,0.0],[0.06,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]],
'ん': [[0.0,0.0,0.0],[0.045,0.015,-0.015],[0.06,0.035,-0.01],[0.075,0.05,0.0],[0.08,0.06,0.0],[0.025,0.05,0.0],[0.025,0.07,0.005],[0.025,0.085,0.01],[0.025,0.1,0.015],[0.0,0.05,0.0],[0.0,0.07,0.005],[0.0,0.085,0.01],[0.0,0.1,0.015],[-0.025,0.05,0.0],[-0.025,0.07,0.005],[-0.025,0.085,0.01],[-0.025,0.1,0.015],[-0.045,0.045,0.0],[-0.045,0.065,0.005],[-0.045,0.08,0.01],[-0.045,0.095,0.015]]
}
# 各文字の説明
DESCRIPTIONS = {
'あ': 'アルファベット「a」', 'い': 'アルファベット「i」', 'う': 'アルファベット「u」',
'え': 'アルファベット「e」', 'お': 'アルファベット「o」', 'か': 'アルファベット「k」',
'き': '影絵の「きつね」', 'く': '数詞手話「9」と同じ', 'け': '親指だけを曲げる',
'こ': 'カタカナ「コ」', 'さ': 'アルファベット「s」', 'し': '数詞手話「7」と同じ',
'す': 'カタカナ「ス」', 'せ': '中指を背に例えて立てる', 'そ': '手話「それ」と同じ',
'た': 'アルファベット「t」', 'ち': 'カタカナ「チ」', 'つ': 'カタカナ「ツ」',
'て': '手そのものを表示', 'と': 'あなた「と」2人で', 'な': 'アルファベット「n」',
'に': 'カタカナ「ニ」', 'ぬ': '手話「盗む」と同じ', 'ね': '手話「根」と同じ',
'の': '手首を動かして、カタカナ「ノ」を空書き', 'は': 'アルファベット「h」',
'ひ': '数詞手話「1」と同じ', 'ふ': 'カタカナ「フ」', 'へ': 'カタカナ「ヘ」',
'ほ': '船の「帆」', 'ま': 'アルファベット「m」', 'み': 'カタカナ「ミ」',
'む': '数詞手話「6」と同じ', 'め': '「目」',
'も': '親指と人さし指を開いた状態から、お互いに合わせる', 'や': 'アルファベット「y」',
'ゆ': '温泉マークの「湯」', 'よ': '「4」本の指を横に出す', 'ら': 'アルファベット「r」',
'り': '人さし指と中指の2本で手首を動かして、カタカナ「リ」を空書き',
'る': 'カタカナ「ル」', 'れ': 'カタカナ「レ」', 'ろ': 'カタカナ「ロ」',
'わ': 'アルファベット「w」', 'を': '指文字「お」を、そのまま自分の方に少し引き寄せる',
'ん': '手首を動かして、カタカナ「ン」を空書き'
}
# 簡略化された指の状態データ(親, 人, 中, 薬, 小 の状態と、手形の説明)
FINGER_SHAPES_SIMPLE = {
'あ': ['伸', '曲', '曲', '曲', '曲', '親指を立てる'],
'い': ['曲', '曲', '曲', '曲', '伸', '小指を立てる'],
'う': ['曲', '伸', '伸', '曲', '曲', '人差指と中指を立てる'],
'え': ['曲', '曲', '曲', '曲', '曲', '指先を軽く曲げて広げる'],
'お': ['他', '曲', '曲', '曲', '曲', '親指と他の指で輪を作る'],
'か': ['曲', '伸', '曲', '曲', '曲', '人差指を立てて、中指を前に出す'],
'き': ['曲', '伸', '曲', '曲', '伸', '人差指と小指を立てて、狐の影絵'],
'く': ['伸', '曲', '曲', '曲', '曲', '親指を立て、手の側面を横に向ける'],
'け': ['曲', '曲', '曲', '曲', '曲', '親指を曲げて、他の指を伸ばす'],
'こ': ['曲', '伸', '伸', '伸', '伸', '指を揃え、手のひらを横(相手側)に向ける'],
'さ': ['曲', '伸', '伸', '伸', '伸', '指を揃えて伸ばす'],
'し': ['曲', '伸', '曲', '曲', '曲', '人差指を立てて、手のひらを横(相手側)に向ける'],
'す': ['曲', '曲', '曲', '曲', '曲', '指を揃えて軽く曲げる'],
'せ': ['曲', '曲', '伸', '曲', '曲', '中指を背に例えて立てる'],
'そ': ['曲', '曲', '伸', '伸', '伸', '手を広げ、手のひらを下に向ける (手話の「それ」)'],
'た': ['曲', '曲', '曲', '曲', '曲', '親指と人差指を曲げ、直角に近い形にする'],
'ち': ['曲', '曲', '曲', '曲', '曲', '手のひらを下にして、指を軽く曲げる'],
'つ': ['曲', '曲', '曲', '曲', '曲', '指を軽く曲げて、手を少し前に出す'],
'て': ['伸', '伸', '伸', '伸', '伸', '手のひらを横(相手側)に向け、手全体を表す'],
'と': ['他', '他', '曲', '曲', '曲', '人差指と中指で輪を作る'],
'な': ['曲', '伸', '伸', '伸', '伸', '指を揃えて伸ばす(「さ」と同じ形)'],
'に': ['曲', '伸', '伸', '曲', '曲', '人差指と中指を立てる(「に」の形に似る)'],
'ぬ': ['伸', '曲', '曲', '曲', '曲', '親指と人差指でつまむ形(手話の「盗む」)'],
'ね': ['曲', '曲', '曲', '曲', '曲', '手を軽く握り、親指を立てる(手話の「根」)'],
'の': ['伸', '伸', '伸', '伸', '伸', '手首を動かして「ノ」の字を空書き'],
'は': ['伸', '伸', '伸', '伸', '伸', '手のひらを相手側に向ける'],
'ひ': ['伸', '曲', '曲', '曲', '曲', '人差指を立てる'],
'ふ': ['伸', '伸', '伸', '伸', '伸', '親指と人差指を少し離し、他は揃える'],
'へ': ['伸', '伸', '伸', '伸', '伸', '手のひらを下に向けて、指を揃える'],
'ほ': ['曲', '伸', '伸', '伸', '伸', '親指を曲げ、他の指を伸ばす(船の帆)'],
'ま': ['伸', '伸', '伸', '伸', '曲', '親指、人差指、中指を伸ばし、薬指、小指を曲げる'],
'み': ['曲', '伸', '伸', '曲', '曲', '人差指と中指を立てる'],
'む': ['伸', '曲', '曲', '曲', '曲', '人差指から薬指までを曲げ、親指と小指を立てる'],
'め': ['曲', '曲', '曲', '曲', '曲', '人差指と中指を曲げて目の形を作る'],
'も': ['他', '他', '曲', '曲', '曲', '親指と人差指をくっつけてから開く'],
'や': ['曲', '曲', '曲', '伸', '伸', '小指と薬指を立てる(アルファベットの「Y」)'],
'ゆ': ['伸', '伸', '伸', '伸', '伸', '指先を揃えて、手のひらを上に向ける(温泉マーク)'],
'よ': ['伸', '伸', '伸', '伸', '曲', '4本の指を横に出す'],
'ら': ['曲', '伸', '伸', '曲', '曲', '人差指と中指を交差させる'],
'り': ['曲', '伸', '伸', '曲', '曲', '人差指と中指を立てて「リ」の字を空書き'],
'る': ['曲', '曲', '曲', '曲', '曲', '人差指と中指を曲げて、小指と薬指を伸ばす'],
'れ': ['伸', '伸', '伸', '伸', '伸', '人差指を少し曲げ、他の指は揃える'],
'ろ': ['曲', '曲', '曲', '曲', '曲', '指を軽く曲げ、手のひらを横(相手側)に向ける'],
'わ': ['伸', '伸', '伸', '伸', '伸', '指を揃えて、手のひらを上に向ける'],
'を': ['他', '曲', '曲', '曲', '曲', '指文字「お」を作り、自分の方に少し引き寄せる'],
'ん': ['伸', '伸', '伸', '伸', '伸', '手首を動かして「ン」の字を空書き']
}
# 手の骨格接続(MediaPipe Hand Landmarker の接続規約に基づく)
HAND_CONNECTIONS = [
(0,1),(1,2),(2,3),(3,4), # 親指
(0,5),(5,6),(6,7),(7,8), # 人差し指
(5,9), # 人差し指の付け根(5)から中指の付け根(9)
(9,10),(10,11),(11,12), # 中指
(9,13), # 中指の付け根(9)から薬指の付け根(13)
(13,14),(14,15),(15,16), # 薬指
(13,17), # 薬指の付け根(13)から小指の付け根(17)
(0,17),(17,18),(18,19),(19,20), # 小指と手首(0)への接続
]
# 手のひら接続の判定用セット
PALM_CONNECTIONS_SET = {
(5, 9), (9, 5), (9, 13), (13, 9), (13, 17), (17, 13), (5,0), (9,0), (13,0), (17,0)
}
# 指のグループ定義 (0=手首)
FINGER_GROUPS = {
'thumb': {0, 1, 2, 3, 4},
'index': {0, 5, 6, 7, 8},
'middle': {0, 9, 10, 11, 12},
'ring': {0, 13, 14, 15, 16},
'pinky': {0, 17, 18, 19, 20},
}
# 指ごとの色設定
FINGER_COLORS = {
'thumb': '#FF5555', # 親指:赤
'index': '#55FF55', # 人差し指:緑
'middle': '#5555FF', # 中指:青
'ring': '#FFFF55', # 薬指:黄
'pinky': '#FF55FF', # 小指:マゼンタ
'palm': '#AAAAAA' # 手のひら:グレー
}
def get_connection_color(conn):
"""接続線の色と太さを取得する"""
start, end = conn
# 手のひらを構成する接続 (付け根同士)
# 太さ: 2 (細い)
if conn in PALM_CONNECTIONS_SET:
return FINGER_COLORS['palm'], 2
# 指ごとの色判定 (太さ: 5 (太い))
# 親指
if start >= 0 and end <= 4:
return FINGER_COLORS['thumb'], 5
# 人差し指
if start >= 5 and end <= 8:
return FINGER_COLORS['index'], 5
# 中指
if start >= 9 and end <= 12:
return FINGER_COLORS['middle'], 5
# 薬指
if start >= 13 and end <= 16:
return FINGER_COLORS['ring'], 5
# 小指
if start >= 17 and end <= 20:
return FINGER_COLORS['pinky'], 5
return FINGER_COLORS['palm'], 2 # デフォルトは手のひらとして細く
def _get_color_with_depth(hex_color, depth, min_depth=-0.1, max_depth=0.1):
"""深度に応じて色を薄くする(白に近づける)"""
# 深度を 0 (手前) 〜 1 (奥) に正規化
depth_ratio = (depth - min_depth) / (max_depth - min_depth)
depth_ratio = max(0.0, min(1.0, depth_ratio))
# 奥にあるものほど強く白を混ぜる
fade_factor = depth_ratio * 0.7
# 16進カラーコード (#RRGGBB) を RGBタプルに変換
r = int(hex_color[1:3], 16)
g = int(hex_color[3:5], 16)
b = int(hex_color[5:7], 16)
# 白 (255, 255, 255) に近づける
r_new = int(r + (255 - r) * fade_factor)
g_new = int(g + (255 - g) * fade_factor)
b_new = int(b + (255 - b) * fade_factor)
return f"#{r_new:02x}{g_new:02x}{b_new:02x}"
class Hand3DViewer:
def __init__(self, root):
self.root = root
self.root.title("日本語指文字 3D Viewer")
self.root.geometry("1000x600")
# 変数
self.current_char = ''
self.rotation_x = 30
self.rotation_y = 45
self.zoom = 1500
self.last_mouse_x = 0
self.last_mouse_y = 0
self.center_offset = (0, 0, 0)
self.buttons = {}
# UI構築
self.create_ui()
# 初期表示
self.select_char('あ')
def create_ui(self):
"""UI構築"""
# タイトル
title = tk.Label(self.root, text="日本語指文字 3D Viewer",
font=('Arial', 16, 'bold'))
title.pack(pady=10)
# メインフレーム
main_frame = tk.Frame(self.root)
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
# 左側:文字選択ボタン
left_frame = tk.Frame(main_frame, width=200)
left_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10))
tk.Label(left_frame, text="文字選択", font=('Arial', 12, 'bold')).pack(pady=5)
# スクロール可能なフレーム
canvas = tk.Canvas(left_frame, width=180)
scrollbar = tk.Scrollbar(left_frame, orient="vertical", command=canvas.yview)
scrollable_frame = tk.Frame(canvas)
scrollable_frame.bind(
"",
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
)
canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
canvas.configure(yscrollcommand=scrollbar.set)
# あ行〜ん行のボタン配置
hiragana_rows = [
('あ行', ['あ', 'い', 'う', 'え', 'お']),
('か行', ['か', 'き', 'く', 'け', 'こ']),
('さ行', ['さ', 'し', 'す', 'せ', 'そ']),
('た行', ['た', 'ち', 'つ', 'て', 'と']),
('な行', ['な', 'に', 'ぬ', 'ね', 'の']),
('は行', ['は', 'ひ', 'ふ', 'へ', 'ほ']),
('ま行', ['ま', 'み', 'む', 'め', 'も']),
('や行', ['や', 'ゆ', 'よ']),
('ら行', ['ら', 'り', 'る', 'れ', 'ろ']),
('わ行', ['わ', 'を', 'ん'])
]
for row_name, chars in hiragana_rows:
tk.Label(scrollable_frame, text=row_name, font=('Arial', 10, 'bold')).pack(anchor='w', pady=(10,2))
char_frame = tk.Frame(scrollable_frame)
char_frame.pack(fill=tk.X)
for char in chars:
btn = tk.Button(char_frame, text=char, width=3,
command=lambda c=char: self.select_char(c))
btn.pack(side=tk.LEFT, padx=2, pady=2)
self.buttons[char] = btn
canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# 右側:3D表示
right_frame = tk.Frame(main_frame)
right_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# 情報表示
info_frame = tk.Frame(right_frame)
info_frame.pack(fill=tk.X, pady=5)
self.char_label = tk.Label(info_frame, text="現在の文字: あ",
font=('Arial', 14, 'bold'))
self.char_label.pack()
# 自動改行対応
self.desc_label = tk.Label(info_frame, text="",
font=('Arial', 11),
justify=tk.LEFT)
self.desc_label.pack(fill=tk.X, padx=10)
# 自動改行対応
self.simple_shape_label = tk.Label(info_frame, text="",
font=('Arial', 10, 'italic'),
fg="#333333",
justify=tk.LEFT)
self.simple_shape_label.pack(fill=tk.X, padx=10)
# info_frame のサイズ変更時にラベルの wraplength を更新
def update_wraplength(event):
margin = 25
self.desc_label.config(wraplength=event.width - margin)
self.simple_shape_label.config(wraplength=event.width - margin)
info_frame.bind('', update_wraplength)
# 3Dキャンバス
self.canvas = tk.Canvas(right_frame, bg='white', width=600, height=400)
self.canvas.pack(fill=tk.BOTH, expand=True, pady=10)
# マウスイベント
self.canvas.bind("", self.on_mouse_down)
self.canvas.bind("", self.on_mouse_drag)
self.canvas.bind("", self.on_mouse_wheel) # Windows/macOS
self.canvas.bind("", self.on_mouse_wheel) # Linux up
self.canvas.bind("", self.on_mouse_wheel) # Linux down
# 操作説明
help_text = "マウスドラッグ: 回転 | マウスホイール: ズーム"
tk.Label(right_frame, text=help_text, font=('Arial', 9)).pack()
def select_char(self, char):
"""文字選択時にボタンのスタイルを変更"""
# 古いボタンのスタイルをリセット
if self.current_char in self.buttons:
self.buttons[self.current_char].config(relief=tk.RAISED, bg=self.root.cget('bg'))
self.current_char = char
# 新しいボタンのスタイルを適用
if self.current_char in self.buttons:
self.buttons[self.current_char].config(relief=tk.SUNKEN, bg='#E0E0E0')
self.update_display()
def update_display(self):
"""表示更新でラベル内容を詳細化"""
self.char_label.config(text=f"現在の文字: {self.current_char}")
self.desc_label.config(text=DESCRIPTIONS[self.current_char])
# FINGER_SHAPES_SIMPLE の情報を更新
simple_shape_data = FINGER_SHAPES_SIMPLE[self.current_char]
fingers = simple_shape_data[:5]
desc = simple_shape_data[5]
finger_labels = ['親', '人', '中', '薬', '小']
# 指の状態を文字列化
shape_str = ", ".join(f"{l}:{s}" for l, s in zip(finger_labels, fingers))
self.simple_shape_label.config(text=f"形状: [{shape_str}] \n{desc}")
# 描画の前に重心を計算
landmarks = LANDMARKS_DATA[self.current_char]
center_x = sum(p[0] for p in landmarks) / len(landmarks)
center_y = sum(p[1] for p in landmarks) / len(landmarks)
center_z = sum(p[2] for p in landmarks) / len(landmarks)
self.center_offset = (center_x, center_y, center_z)
self.draw_3d_hand()
def rotate_point(self, x, y, z):
"""3D回転"""
# X軸回転
rad_x = math.radians(self.rotation_x)
y_new = y * math.cos(rad_x) - z * math.sin(rad_x)
z_new = y * math.sin(rad_x) + z * math.cos(rad_x)
y, z = y_new, z_new
# Y軸回転
rad_y = math.radians(self.rotation_y)
x_new = x * math.cos(rad_y) + z * math.sin(rad_y)
z_new = -x * math.sin(rad_y) + z * math.cos(rad_y)
x, z = x_new, z_new
return x, y, z
def project_point(self, x, y, z):
"""2D投影と深度による遠近法"""
# 重心からのオフセットを適用
x -= self.center_offset[0]
y -= self.center_offset[1]
z -= self.center_offset[2]
# 回転
x, y, z = self.rotate_point(x, y, z)
# 投影 (遠近法)
depth_factor = self.zoom / (self.zoom + z)
canvas_width = self.canvas.winfo_width()
canvas_height = self.canvas.winfo_height()
# 座標をキャンバスの中心に合わせる
proj_x = canvas_width / 2 + x * depth_factor * self.zoom
proj_y = canvas_height / 2 - y * depth_factor * self.zoom # Y軸は反転
return proj_x, proj_y, z # Z (奥行き) も返す
def draw_3d_hand(self):
"""3D手の骨格を描画"""
self.canvas.delete("all")
landmarks = LANDMARKS_DATA[self.current_char]
# 投影された座標を保存 (ソート用)。ランドマーク番号も保持する
projected_points = []
for index, point in enumerate(landmarks):
proj_x, proj_y, z = self.project_point(point[0], point[1], point[2])
projected_points.append(((proj_x, proj_y, z), index))
# 描画要素をリストに保存 (奥から手前の順にソートして描画するため)
draw_elements = []
# 1. 接続線 (骨格)
for conn in HAND_CONNECTIONS:
start_index, end_index = conn
# 投影された始点と終点の情報
(x1, y1, z1), _ = projected_points[start_index]
(x2, y2, z2), _ = projected_points[end_index]
# 接続線の色と太さを取得
color, width = get_connection_color(conn)
# 線の平均深度
avg_z = (z1 + z2) / 2
# 深度に基づく色の調整
shaded_color = _get_color_with_depth(color, avg_z)
# 描画順リストに追加
# (avg_z, 'line', coords, options)
draw_elements.append((avg_z, 'line', (x1, y1, x2, y2), {'fill': shaded_color, 'width': width}))
# 2. ランドマーク (関節点)
dot_size = 5
for (x, y, z), index in projected_points:
# 指ごとに色を変える
if index in FINGER_GROUPS['thumb']:
fill_color = FINGER_COLORS['thumb']
elif index in FINGER_GROUPS['index']:
fill_color = FINGER_COLORS['index']
elif index in FINGER_GROUPS['middle']:
fill_color = FINGER_COLORS['middle']
elif index in FINGER_GROUPS['ring']:
fill_color = FINGER_COLORS['ring']
elif index in FINGER_GROUPS['pinky']:
fill_color = FINGER_COLORS['pinky']
else: # 手首や手のひら
fill_color = FINGER_COLORS['palm']
# 深度に基づく色の調整
shaded_color = _get_color_with_depth(fill_color, z)
# 描画順リストに追加
# (z, 'oval', coords, options)
draw_elements.append((z, 'oval', (x-dot_size, y-dot_size, x+dot_size, y+dot_size), {'fill': shaded_color, 'outline': '#333333', 'width': 1}))
# 3. 描画順のソートと実行 (Zが大きい(奥)ものから先に描く)
draw_elements.sort(key=lambda item: item[0], reverse=True)
for z_depth, element_type, coords, options in draw_elements:
if element_type == 'line':
self.canvas.create_line(*coords, **options)
elif element_type == 'oval':
self.canvas.create_oval(*coords, **options)
# --- マウス操作 ---
def on_mouse_down(self, event):
self.last_mouse_x = event.x
self.last_mouse_y = event.y
def on_mouse_drag(self, event):
dx = event.x - self.last_mouse_x
dy = event.y - self.last_mouse_y
# Y軸ドラッグでX軸回転 (上下のドラッグ)
self.rotation_x = (self.rotation_x - dy * 0.5) % 360
# X軸ドラッグでY軸回転 (左右のドラッグ)
self.rotation_y = (self.rotation_y + dx * 0.5) % 360
self.last_mouse_x = event.x
self.last_mouse_y = event.y
self.draw_3d_hand()
def on_mouse_wheel(self, event):
# Windowsの場合: event.delta は 120 または -120
# Linuxの場合: event.num は 4 (up) または 5 (down)
if event.num == 4 or event.delta > 0:
# ズームイン
self.zoom = min(self.zoom + 100, 5000)
elif event.num == 5 or event.delta < 0:
# ズームアウト
self.zoom = max(self.zoom - 100, 500)
self.draw_3d_hand()
if __name__ == "__main__":
root = tk.Tk()
app = Hand3DViewer(root)
root.mainloop()