Bu dosya okunarak cikarildi, tahmin yok. Her iddianin yaninda dosya:satir var.
§1–8 Character DNA Pro 0.8.7 (Blender 5.1) kaynagindan cikarildi. Kullanicinin fiilen calistigi surum Blender 5.0 + meta_human_dna 0.5.4; mimari ayni, isimler farkli. Karsilik tablosu ve 5.0'da yapilan dogrulamalar: §9.
| Addon | Character DNA Pro 0.8.7 |
| Modul | bl_ext.polyhammer_com.character_dna_pro |
| Kurulum yolu | %APPDATA%\Blender Foundation\Blender\5.1\extensions\polyhammer_com\character_dna_pro |
| Blender | 5.1.2, Python 3.13.9 |
| Dogrulama tarihi | 2026-08-18 |
Asagida <addon> = yukaridaki kurulum yolu.
Dikkat: Diskte baska kopyalar da var.
5.1/extensions/polyhammer/character_dna_prokayitli repo degil, bayat.scripts/addons/meta_human_dnaise 0.5.4 — eski isim, API'si farkli (bkz. §9). Grep atarken yanlis kopyayi okumak kolay; 5.1'de kayitli repo adipolyhammer_com.
RigInstance bir PropertyGroup, sahnedeki listede duruyor:
- Sinif:
<addon>/rig_instance.py:314 - Liste:
<addon>/properties.py:456→rig_instance_list - Aktif indeks:
<addon>/properties.py:457→rig_instance_list_active_index
Sahne property adi iki tane: constants.py:313 SIBLING_EDITIONS = {"character_dna": "character_dna_pro", ...}
ve properties.py:519-521 her iki ismi de bpy.types.Scene uzerine bagliyor. Introspection
ciktisi ikisinin de var oldugunu dogruladi. Bu yuzden kodda tek isim sabitlenmez:
for name in ("character_dna_pro", "character_dna"):
properties = getattr(context.scene, name, None)
...instance.face_board bir armature Object'i (rig_instance.py:157-168, animation_data/action
uzerinden kullaniliyor). GUI kontrol isimleri dogrudan bu armature'un pose bone adlaridir.
update_head_gui_control_values(override_values) — <addon>/rig_instance.py:1508
for index in range(self.head_dna_reader.getGUIControlCount()):
full_name = self.head_dna_reader.getGUIControlName(index) # "CTRL_C_jaw.ty"
control_name, axis = full_name.split(".")
axis = axis.rsplit("t", -1)[-1].lower() # "ty" -> "y"
if override_values:
value = override_values.get(control_name, {}).get(axis)
...
else:
pose_bone = self.face_board.pose.bones.get(control_name)
value = getattr(pose_bone.location, axis)
self.head_instance.setGUIControl(index, value)Iki giris yolu var:
- Pose bone location yazmak (override yok) — addon kemikten okur.
charfacebunu kullanir: keyframe'lenebilir, viewport'ta gorunur, addon'un kendi bake/export akisiyla uyumlu. override_valuesgecmek — addon'un kendi yorumu: "can be used for baking the values to an action". Kemige dokunmadan degerlendirme yapar.
Ters yon de var: apply_gui_controls_to_face_board() — rig_instance.py:1575 RigLogic'teki GUI
degerlerini face board kemiklerine geri yazar.
Plan "Raw Control'e yaz, Unreal'in mapping'iyle birebir ayni olur" diyordu. Kaynakta durum tersine:
(a) update_head_raw_control_values ifade kontrollerini zaten atliyor — rig_instance.py:1456:
control_name, axis = full_name.split(".")
if not axis.startswith("q"): # <-- sadece quaternion eksenleri
continueDNA'da raw kontrol isimleri CTRL_expressions.browDownL formatinda; split(".") sonrasi
axis = browDownL, q ile baslamiyor → atlaniyor. Bu fonksiyon sadece RBF/kemik surucusu
quaternion kontrolleri icin. 263 raw kontrolun 251'i CTRL_expressions.*, yani buradan yazilmiyor.
(b) Yazilsa bile ayni karede eziliyor — evaluate() (rig_instance.py:2000) sirasi:
update_head_gui_control_values() # GUI degerleri
head_manager.mapGUIToRawControls() # <-- raw kontroller GUI'den YENIDEN uretilir
update_head_raw_control_values() # sadece evaluate_rbfs acikken, sadece .q*
head_manager.calculate()
setRawControl() ile yazilan CTRL_expressions.* degeri mapGUIToRawControls() tarafindan
uzerine yazilir. Raw yazmak icin evaluate() akisini tamamen bypass etmek gerekirdi — o zaman da
kemik/shape key/texture mask guncellemelerini elle yapmak zorunda kalirdik.
Sonuc: face board (GUI) dogru giris noktasi. Planin A secenegi.
evaluate(component, dependency_graph) — rig_instance.py:2000.
Basinda bir bayrak var, atlanmasi bake performansini oldurur:
if window_manager_properties.evaluate_dependency_graph:
window_manager_properties.evaluate_dependency_graph = False
...
finally:
window_manager_properties.evaluate_dependency_graph = TrueBayrak False iken evaluate() hicbir sey yapmadan doner. Bake sirasinda bunu bilerek
False yapip 150 kareyi yazar, sonunda Trueya cevirip tek sefer evaluate() cagiririz.
Aksi halde her keyframe_insert depsgraph handler'ini tetikler ve RigLogic bosuna yuzlerce kez kosar.
get_dna_reader() — <addon>/dna_io/misc.py:41.
API 0.5.4'ten 0.8.7'ye degisti, ezberden yazilmaz:
| 0.5.4 (eski) | 0.8.7 (guncel) | |
|---|---|---|
| modul | riglogic |
dna |
| open mode | riglogic.OpenMode.Binary |
dna.OpenMode_Binary |
| data layer | riglogic.DataLayer.All |
dna.DataLayer_All |
Binding'ler derlenmis .pyd: <addon>/bindings/windows/x64/{py311,py313}/. bindings/__init__.py
bir IsolatedModuleLoader meta_path hook'u kuruyor, bu yuzden sys.pathe ekleyip
import dna demek calismaz — paket uzerinden gitmek gerekiyor:
importlib.import_module(f"{addon_module}.bindings").dnaModul adi da sabitlenmez, addon_utils.modules() ile aranir (bkz. scripts/01_dump_dna_controls.py).
scripts/01_dump_dna_controls.py ciktisi (varsayilan MetaHuman head.dna):
| GUI kontrol | 174 — CTRL_L_brow_raiseIn.ty formatinda, face board kemik adlari |
| Raw kontrol | 263 — CTRL_expressions.browRaiseInL formatinda (251'i CTRL_expressions.*) |
| GUI→raw satiri | 259, surulen raw kontrol 251 |
GUI→raw parcali dogrusal: raw = slope * gui + cut, gui ∈ [from, to].
-
243 raw kontrolun tek GUI girdisi var, cogu identity (slope=1, cut=0, 0..1)
-
8 tanesinin iki girdisi var:
mouthLipsSticky*Ph1/Ph2,neckSwallowPh1..4— ayni slider uzerinde ucgen rampalar (faz kontrolleri). Tersi belirsiz, tabloya alinmiyor. -
88 satir identity degil. Onemli kalip: bir slider'in negatif yarisi baska bir raw kontrolu suruyor:
CTRL_expressions.eyeBlinkL <- CTRL_L_eye_blink.ty from=0..1 slope=+1 CTRL_expressions.eyeWidenL <- CTRL_L_eye_blink.ty from=-1..0 slope=-1Yani ARKit'in
eyeBlinkLeftveeyeWideLeftkanallari ayni ekseni ters yonde suruyor. Bagimsiz yazilamazlar; toplanmalari gerekir.core/mapping.pybunu yapiyor, testitests/test_mapping.py::test_blink_ve_wide_toplanip_birbirini_goturuyor.
mapping/arkit_to_mh.json, scripts/02_build_mapping.py ile uretiliyor:
ARKit pozu --(Epic posemap)--> raw CTRL_expressions.* --(GUI→raw tersi)--> GUI ekseni
Kaynak: Dylanyz/ARKitRemap reposundaki PA_MetaHuman_ARKit_Mapping.posemap.json.
Guven notu: bu tablo Epic'in asset'inin birebir kopyasi degil, editor python'uyla
orneklenerek cikarilmis (poseIndexToTimeRule: i/(N-1), baseline cikarma). Sonuclari:
- posemap'teki 77 curve isminin 76'si DNA'daki raw kontrollere birebir eslesti
(
ctrl_expressions_browraiseinl↔CTRL_expressions.browRaiseInL; kucuk harf + nokta→alt cizgi). Eslesmeyen tek isimctrl_expressions_tonguerolldown, varsayilan DNA'da yok. browLateralL0.031 agirliklacheekPuff,tongueOutgibi alakasiz pozlarda gorunuyor — ornekleme artefakti.epsilon=0.05ile eleniyor (11 kayit).- Pose asset'inde
Pose_4..Pose_17isimli 14 girdi var, hepsi ayni degerlere ornekleniyor vetongueOutile ozdes. Ayirt edilememis ekstra dil pozlari; tabloya alinmiyor. - Kapsama: MediaPipe'in 51 blendshape'inin 50'si. Eksik olan tek kanal
mouthClose(posemap'te sifirdan farkli kaydi yok).tongueOuttabloda var ama MediaPipe uretmiyor — sadece iPhone Live Link icin.
Dogrulanmadi: agirliklarin Unreal'deki gorsel sonuca birebir esitligi. Epic'in asset'ini
Unreal'den T3D olarak export edip --posemap yerine onu vermek daha saglam olur; sema ayni kalir.
Yukaridaki referanslar 0.8.7 icin. Kullanicinin Blender 5.0'inda 0.5.4 kurulu ve mimari ayni,
isimler farkli. <addon50> = %APPDATA%\Blender Foundation\Blender\5.0\scripts\addons\meta_human_dna.
| Kavram | 0.5.4 (Blender 5.0) | 0.8.7 (Blender 5.1) |
|---|---|---|
| Ana dosya | rig_logic.py |
rig_instance.py |
| Instance sinifi | RigLogicInstance — rig_logic.py:387 |
RigInstance — rig_instance.py:314 |
| Sahne property | scene.meta_human_dna (MetahumanSceneProperties) |
scene.character_dna_pro / character_dna |
| Instance listesi | rig_logic_instance_list — properties.py:213 |
rig_instance_list — properties.py:456 |
| GUI yazma | update_head_gui_control_values() — rig_logic.py:1453 |
rig_instance.py:1508 |
Raw yazma (sadece .q*) |
rig_logic.py:1401 |
rig_instance.py:1456 |
evaluate() |
rig_logic.py:1925 |
rig_instance.py:2000 |
| DNA reader modulu | riglogic.OpenMode.Binary, DataLayer.All |
dna.OpenMode_Binary, DataLayer_All |
| Binding'ler | bindings/windows/amd64/ sadece cp311 |
bindings/windows/x64/{py311,py313}/ |
Blender 5.0'in python'u 3.11, 0.5.4'un binding'leri de cp311 → uyumlu.
Degismeyenler (charface'in dayandigi her sey):
instance.face_boardarmature Object'i, GUI kontrol isimleri = pose bone adlarievaluate()icindeki sira: GUI →mapGUIToRawControls()→ raw →calculate(), yani §4'teki "raw yazma" gerekcesi 0.5.4'te de gecerli (rig_logic.py:1503)window_manager.<addon>.evaluate_dependency_graphbayragi (§5)
Blender 5.0'da dogrulandi: charface yuklendi, MetahumanSceneProperties bulundu, WM bayragi
mevcut, mapping yuklendi (51 kanal / 64 kemik), jawOpen=1.0 -> CTRL_C_jaw.y=0.999999,
eyeBlinkLeft+eyeWideLeft 0.5 -> 0.0.
DNA'da 263 raw kontrolun 12'si ifade degil, kemik quaternion'u:
neck_01.qx/qy/qz/qw neck_02.qx/qy/qz/qw head.qx/qy/qz/qw
Yani surucu kemikler: head, neck_01, neck_02. Bunlarin rotasyonu RigLogic'e
girdi -- boyun/kafa duzelticilerini tetikliyorlar (update_head_raw_control_values
sadece .q* eksenlerini isliyor, bkz. §4a).
Kritik olan: RigLogic 870 eklemin hepsini suruyor, head dahil. Buna ragmen bu uc kemige
yazmak guvenli, cunku addon bunlari bilerek atliyor:
# 0.5.4 rig_logic.py:1630 (0.8.7'de rig_instance.py icinde ayni kontrol)
if name in self.head_driver_bone_names:
continuehead_driver_bone_names (rig_logic.py:1079) tam olarak yukaridaki .q* isimlerinden
uretiliyor. Yani surucu kemiklere yazilan rotasyon ezilmiyor; diger 867 eklem RigLogic'in
cikardigi degerle yeniden yaziliyor.
charface bu yuzden kafayi head kemigine yaziyor (resolve_head_bone), ve yazmadan once
kemigin gercekten surucu listesinde oldugunu dogruluyor.
Iki tuzak:
- Mutlak degil bagil yaz. MediaPipe kameraya gore mutlak yonelim veriyor; dogrudan
yazilirsa karakterin kafasi kameranin onunde nasil duruyorsan oyle sabitlenir.
core/head.py:HeadTrackerilk 30 kareden notr referans kurup farki uyguluyor. - Eksen eslemesi rig'e gore degisiyor. Tek dogru donusum yok; operator'de
head_axispreset'i olarak disari acildi (varsayilanX-ZY). Kafa yanlis yone donuyorsa buradan degistirilir -- kodda sabit degil.
Gercek MetaHuman rig'inde iki gozlemle kuruldu (teorik degil):
| Gozlem | Sonuc |
|---|---|
| yaw, kemigin Z eksenine gonderildi -> karakter asagi/yukari bakti | Z = pitch |
| yaw->Y ve roll->X iken ikisi birbirinin yerine calisti | Y = roll, X = yaw |
Yani kafa kemiginin yerel eksenleri: X = yaw, Y = roll, Z = pitch.
Bu, Blender'in "kemik yerel Y ekseni boyunca uzanir" kuralindan cikan sezgiye uymuyor
(Y twist olsaydi yaw beklenirdi). MetaHuman kemikleri Unreal'den geldigi icin yonelim
farkli. Tam da bu yuzden kodda sabitlenmedi: core/head.py:DEFAULT_MAPPING sadece
baslangic degeri, gercek ayar sahnede (scene.charface) ve panelden degistiriliyor.
Ayar sahnede saklandigi icin koddaki varsayilan degistiginde mevcut .blend dosyalari
eski degeri tutmaya devam eder; charface.reset_calibration operator'u ikisini esitler.
Dogrulanmadi: isaretler (Ters bayraklari). Eksenler oturduktan sonra yon kontrolu gerekiyor.
DNA'da agiz-dudak-cene-dil bolgesinde 110 GUI kontrolu var; posemap'ten turetilen tablo bunlarin 46'sini suruyor. Kalan 64'u MediaPipe'in uretmedigi ince kontroller (lipBite, thickness, sticky, cornerSharpness, tongue detaylari) -- bunlar 51 ARKit kanalindan turetilemez, normal.
Ama biri onemliydi ve eksikti: CTRL_L/R_mouth_lipsTogetherU/D.
ARKit'te bunu mouthClose surer: "cene aciligindan BAGIMSIZ olarak dudaklarin kapanma
miktari". Bu olmadan "m", "b", "p" seslerinde cene hafif acikken dudaklarin kapanmasi
gerekirken agiz acik kaliyor -- konusmayi yapay gosteren birincil sebep.
Epic'in pose asset'inde MouthClose icin sifirdan farkli hicbir kayit yok, yani
turetilemedi. scripts/02_build_mapping.py:AUTHORED icinde anlamdan elle yazildi
(dort lipsTogether kontrolu, agirlik 1.0) ve tabloda authoredChannels olarak
isaretlendi. Epic'in gercek agirliklari farkli olabilir.
Kapsama artik 51/51, kanal sayisi 84 -> 88.
mouthPucker alti kontrolu suruyor ve bunlarin ikisi (funnelU/D) mouthFunnel
tarafindan da suruluyor. MediaPipe ikisini sik sik birlikte tetikliyor; toplama +
kirpma kuralinda toplam doyuma gidip agzi buzuk kilitliyor. Cozum kod degil ayar:
panelde Dudaklar kazanci ile kisiliyor.
Tek one-euro ayari tum yuze uymuyor. Konusma saniyede birkac kez sekil degistiriyor;
kaslara uygun yumusatma dudaklari lapa yapiyor. core/filters.py artik uc grup
kullaniyor: blink (min_cutoff 5.0), agiz/cene (3.0), geri kalan (1.0).
Duzen Epic tarafindan resmi belgelenmemis ve topluluk kaynaklari celisiyor:
lucasjinreal/llv README'si isimlerin degerlerden SONRA geldigini soyluyor, digerleri ONCE.
aelzeiny/Animoji reposunda gercek bir iPhone'dan netcat ile yakalanmis ham UDP kaydi var
(experimentation/livelink.udp, 179 KB). Cozucu ona karsi kosuldu:
| Sirali cozulen paket | 546 |
| Basarisiz | 0 |
| Kullanilan yol | hepsi structured (yedek tarayiciya hic dusmedi) |
| Paket boyutu | 319 bayt (1 + 4+36 + 4+13 + 16 + 1 + 61*4) |
Yani isimler degerlerden ONCE geliyor, llv'nin README'si yanlis. Dogru duzen:
uint8 surum (6)
int32 + bytes cihaz kimligi (UUID, 36 bayt)
int32 + bytes konu adi
int32 kare numarasi
float alt kare
int32 fps
int32 payda
uint8 kanal sayisi (61)
float[61] degerler
Hepsi big-endian. abclop99/livelinkface/pylivelinkface.py:218-242 ayni sirayi kullaniyor,
bagimsiz ikinci dogrulama.
Kayittan bir kare tests/fixtures/livelink_frame.bin olarak saklandi (cihaz UUID'si ve telefon
adi ayni uzunlukta yer tutucuyla degistirildi -- baskasinin kimligini repoya gommeyiz).
O karede TongueOut = 1.0, yani dil kanalinin gercekten calistigi da dogrulanmis oldu.
Hedef: MHA'nin yaptigi isi kendimiz yapmak. Ozu analysis-by-synthesis -- gozlemi (landmark/derinlik) rig'in parametre uzayinda en iyi aciklayan kontrol vektorunu bulmak. Isin zor kismi olan ileri model bizde zaten var: DNA + RigLogic.
| Tek RigLogic degerlendirmesi | 0.234 ms |
| Cikti okumayla | 0.486 ms |
| Numerik Jacobian, 174 parametre | 85 ms/iterasyon |
| Tek kare, 10 iterasyon | 0.8 s |
| 300 kare (10 sn @30fps) | ~4 dakika |
Offline cozum icin fazlasiyla yeterli.
getRawJointOutputs() + getBlendShapeOutputs() veriyor, vertex konumu
degil. Zinciri kendimiz kuruyoruz (core/facemodel.py):
notr vertex + blendshape deltalari -> linear blend skinning = vertex
Joint cikti semantigi addon kaynagindan okundu (rig_logic.py:1613),
tahmin degil:
- joint basina 9 deger: oteleme(3) + rotasyon(3) + olcek(3)
- hepsi delta, uygulama
notr + delta - rotasyon derece, Euler sirasi XYZ
SCALE_FACTOR = 100(constants.py:38), notr rotasyonlar da derece (dna_io/importer.py:476)
Ilk deneme jointlerin notr dunya konumlarina yakinliga bakiyordu ve 0 vertex buldu. Sebep: DNA jointleri ebeveyne gore tutuyor ve dogru dunya konumu icin ebeveyn ROTASYONLARININ da zincirlenmesi gerekiyor; sadece oteleme toplamak konumlari kaydiriyor.
Skin agirligi hem bu sorunu atlatiyor hem daha dogru bir tanim: bir vertex, agirliginin cogu goz kapagi jointlerine gidiyorsa goz kapagindadir. Bu rig'in kendi tanimi, geometrik tahmin degil.
--region eyelid --threshold 0.5 -> 2168 vertex, 737 blendshape
hedefinden 311'i bu bolgeye dokunuyor.
Ileri modelin dogrulugu tek bir testle kanitlanir: sifir kontrolde cikti notr vertexlerle ayni olmali.
world @ bind^-1 birim matristen sapma |
5.7e-14 (float64 epsilonu) |
| Notr yeniden uretimde mutlak hata | 2.4e-05 cm = 0.00024 mm |
| Bagil hata | 1.46e-07 |
| float32 epsilonu | 1.19e-07 |
Yani matris zinciri tam dogru; kalan sapma yalnizca DNA'nin float32 saklanmasindan geliyor ve olcecegimiz her seyin cok altinda.
Deform suresi 2168 vertex icin 3.3 ms.
Ilk deform 3.04 ms'ti ve %54'u 870 jointlik hiyerarsi zinciriydi. Vertex sayisini dusurmek fayda etmedi (100 vertexte bile 1.96 ms) cunku zincir sabit maliyet.
Iki gozlem cozdu:
- Goz kapagi bolgesi 870 jointin sadece 128'ini kullaniyor (skinlenen 95 + atalari). Gerisini hesaplamak gereksiz.
- Zincir sadece 8 seviye derinlikte. Ayni seviyedeki jointler ayni anda hesaplanabilir -> python dongusu joint sayisi kadar degil DERINLIK kadar.
| once | sonra | |
|---|---|---|
| joint zinciri | 1.65 ms | 0.097 ms (17x) |
| toplam deform | 3.04 ms | 1.46 ms (2.1x) |
| notr hata | 2.381e-05 | 2.381e-05 (degismedi) |
Iterasyon maliyeti = (serbest parametre + 1) x (RigLogic 0.486 ms + deform 1.46 ms):
| serbest parametre | 2 iterasyon | |
|---|---|---|
| 4 | 19.5 ms | gercek zaman |
| 8 | 35.1 ms | sinirda |
| 16 | 66.3 ms | offline |
| 174 | 682 ms | offline |
Yani kucuk parametre kumesiyle gercek zaman bugun CPU'da mumkun. Goz kapagi durumu tam olarak bu: 4 kontrol.
Etmez: RigLogic derlenmis bir CPU kutuphanesi (Epic/Poly Hammer ikilisi). GPU'ya tasinamaz. Numerik Jacobian N+1 sirali RigLogic cagrisi gerektiriyor ve yuksek parametre sayisinda baskin maliyet bu.
Eder: Ileri modeli PyTorch'ta turevlenebilir yeniden yazarsak autograd tek geri gecisle tam gradyan veriyor -- N+1 carpani tamamen kalkiyor. Bu 2x degil, O(N) -> O(1) kazanci. Ustune GPU kareleri batch'liyor.
Parcalari zaten elimizde: GUI->raw parcali dogrusal diziler (§7), joint
gruplari (getJointGroupValues, dogrusal matrisler), blendshape deltalari,
skin agirliklari. En zor kisim olan semantik tersine muhendislik bitti.
Makinede torch 2.11.0+cu128 ve RTX 5070 Ti (sm_120) calisir durumda.
- Optimizasyon dongusu (Gauss-Newton / least squares)
- Landmark <-> vertex karsiligi (24049 vertexten hangisi MediaPipe'in hangi noktasi)
- Derinlik verisi (Record3D gibi; Live Link'in MHA modu kapali format)
- Zamansal duzenlileştirme
core/riglogic_torch.py RigLogic'i sifirdan yeniden yaziyor. Amac autograd:
numerik Jacobian N+1 sirali RigLogic cagrisi gerektiriyor ve RigLogic
derlenmis CPU kodu -- paralellestirilemiyor.
GUI (174)
| parcali dogrusal: raw = slope * gui + cut, gui in [from, to]
raw (263)
| PSD (545): refere edilen kontrollerin CARPIMI
| RBF poz kontrolleri (18): joint rotasyonlarindan, disaridan verilir
kontrol (826)
| 122 joint grubu -> tek (7830 x 826) matrise indirilebilir
joint deltalari (7830) + blendshape agirliklari (782, birebir gather)
1. Kontrol vektoru 808 degil 826. Once raw + PSD varsaymistim. Joint
grubu girdi indeksleri 825'e gidince patladi. Eksik olan
getRBFPoseControlCount() = 18 -- RBF poz kontrolleri PSD'lerin ardina
ekleniyor.
2. psdValues bir CARPAN DEGIL. Her terimi kendi degeriyle carpiyordum.
"Hepsi +1" ornegi patladi (PSD 453: sutun 191 deger 4.0, sutun 108 deger
1.0 -> benim 4.0, RigLogic 1.0). Dort hipotez 32 gercek ornege karsi
denendi:
| yorum | hata |
|---|---|
| carpim x deger | 3.0e+00 |
| carpim / deger | 7.5e-01 |
| carpim x ilk deger | 3.0e+00 |
| sadece carpim | 2.2e-08 |
Ikisini de yakalayan sey gercek RigLogic'e karsi dogrulama oldu; rastgele gercekci girdiler her ikisini de gizliyordu (hata 1e-7), sadece "hepsi +1" gibi sinir durumu ortaya cikardi.
32 ornek (notr, hepsi +1, hepsi -1, 29 rastgele):
| joint deltalari, bagil hata | 3.3e-07 |
| blendshape, mutlak hata | 3.0e-08 |
| notr girdi | tam sifir |
float32 kaynak hassasiyeti mertebesinde -- yeniden yazim dogru.
122 grup ayri ayri hesaplanirsa 121 kernel baslatmasi oluyor; tek kare gradyaninda bu tamamen overhead. Gruplari tek (7830 x 826) matriste birlestirmek (25 MB, %14.9 dolu) cozuyor:
| 121 grup | tek GEMM | |
|---|---|---|
| CPU tek kare ileri+geri | 11.40 ms | 3.55 ms |
| CUDA tek kare ileri+geri | 37.70 ms | 4.15 ms |
Tam 174-parametre gradyani, tek kare:
| sure | numerik Jacobian'a gore | |
|---|---|---|
| numerik (RigLogic C++) | 341.1 ms | 1x |
| autograd CPU | 4.51 ms | 75x |
| autograd CUDA | 5.46 ms | 62x |
Ikisi de 30 fps butcesinde (33 ms). Tek karede CPU daha hizli -- GPU'nun kernel baslatma maliyeti bu boyutta baskin. GPU batch'te kazaniyor: 64 karelik batch'te kare basina 0.71 ms (CPU 1.77 ms).
Yani: gercek zaman tek kare -> CPU, offline toplu cozum -> GPU.
Olculdu: DNA'da 23 goz kontrolu var, ARKit tablosuyla sadece 8'i suruluyor. Kalan 15'i (goz kapaklari, kapak baskisi, goz bebegi, kirpik tweaker'lari) ARKit'in 52 kanalinda karsiligi olmadigi icin olu duruyor.
Olculemezler ama TURETILEBILIRLER -- anatomi bunlari bakis yonune baglar.
core/realism.py esleme ciktisindan turetip ustune ekliyor; kaynagi
(webcam / iPhone / replay) umursamiyor.
| GUI ekseni | negatif | pozitif |
|---|---|---|
CTRL_*_eye.ty |
asagi bak | yukari bak |
CTRL_*_eye_eyelidU.ty |
ust kapak KALKAR (eyeUpperLidUp) |
kapak gevser (eyeRelax) |
CTRL_*_eye_eyelidD.ty |
alt kapak iner | alt kapak kalkar |
CTRL_*_eye_lidPress.ty |
— | kapak baskisi |
Yani yukari bakis pozitif ama kapagi kaldirmak icin eyelidU NEGATIF
olmali; kural eyelidU = -k * bakis. Isaret ters yazilirsa goz yukari
bakarken kapak inip goz kapanmis gorunur -- testler bunu tutuyor.
Kirpma sirasinda takip soneriyor (1 - blink), yoksa kapak takibi
kapanan gozu geri aciyor.
Gercek iPhone kaydinda olculdu: kanallar 1.0'a ulasmiyor.
| kanal | gercek tepe |
|---|---|
| eyeBlinkLeft/Right | 0.917 |
| mouthSmileLeft | 0.865 |
| browInnerUp | 0.858 |
| jawOpen | 0.973 |
Kisi gozunu tamamen kapatiyor ama ARKit 0.92 diyor; rig 1.0'da tam kapanacak sekilde kurulu oldugundan goz kapagi her zaman biraz acik kaliyor. "Olu bakis" hissinin somut kaynagi bu.
ChannelProfile.input_max gozlenen tepeyi 1.0'a normalize ediyor.
RangeLearner bir ROM cekiminden ogreniyor; floor=0.35 altindaki
kanallara dokunmuyor (hic tetiklenmemis kanali normalize etmek 20x
kazanc demek olur ve gurultuyu patlatir).
Ilk hipotezim ARKit'in kirpma egrisini yeniden sekillendirmekti. Gercek kayitta olculdu ve hipotez CURUTULDU:
| olculen | literatur | |
|---|---|---|
| kapanma | 89 ms | 50-100 ms |
| acilma | 194 ms | 150-300 ms |
| oran | 2.19 | ~2-3 |
| kirpma sikligi | 19.8/dk | 15-20/dk |
ARKit kirpmayi zaten fizyolojik olarak dogru veriyor. realism.blink_curve
duruyor ama varsayilani kimlik.
Buna karsilik gercek bir kusur bulundu: canli modal kuyrugu bosaltip sadece son paketi kullaniyordu. Kapanma fazi 60 fps'te 3-7 kare, yani tepe noktasi atilan pakette kalabiliyordu. Artik bir tikte birden fazla paket gelirse kanal basina maksimum aliniyor.
core/solver.py -- gozlemden rig kontrollerini cozer. Zincir bastan sona
turevlenebilir:
GUI (174) -> TorchRigLogic -> joint/blendshape -> TorchFaceModel -> vertex
-> baryantrik -> landmark -> kayip
MediaPipe landmark'lari kafa pozunu iceriyor ve kamera ic parametrelerini bilmiyoruz. 2D yeniden izdusum yerine once rijit hizalama (Procrustes: donme + oteleme + tek olcek) yapiliyor; kafa pozu ve olcek ayiklaninca geriye sadece ifade kaliyor.
Problem kotu kosullu: farkli kontrol kombinasyonlari benzer landmark dizilimi uretiyor. Sadece L2 ile cozuldugunde 6 kontrollu bir ifadede 23 kontrol yanlislikla tetikleniyordu.
Gercek ifadeler az sayida kontrol kullanir; bu bir onsel ve L1 ile dayatilabilir. Sentetik gidis-donus testiyle olculdu:
| L2 | L1 | RMS | kontrol hatasi | yanlis tetik |
|---|---|---|---|---|
| 1e-4 | 0 | 0.118 mm | 0.093 | 23 |
| 1e-3 | 0 | 0.183 mm | 0.184 | 3 |
| 1e-4 | 5e-4 | 0.112 mm | 0.066 | 1 |
| 1e-4 | 3e-3 | 0.436 mm | 0.212 | 0 |
Sadece L2'yi buyutmek her seyi bastiriyor (hata 2 katina cikiyor); L1 yanlis tetikleri kirparken dogrulugu da iyilestiriyor.
L1'in bilinen yan etkisi tahminleri sifira dogru buzmesi (kirpma 0.50 yerine 0.435 cikiyordu). Careci standart: L1 aktif kumeyi bulsun, sonra sadece o kume uzerinde L1'siz yeniden coz.
| asama | ort. kontrol hatasi | RMS |
|---|---|---|
| L1 | 0.066 | 0.1118 mm |
| + yanlilik giderme | 0.002 | 0.0060 mm |
Hedef 6 kontrol; cozucu 6 kontrol buldu, hepsi 0.006 hata payinda:
| kontrol | hedef | bulunan |
|---|---|---|
| CTRL_C_jaw | 0.60 | 0.600 |
| CTRL_L_mouth_cornerPull | 0.80 | 0.800 |
| CTRL_R_mouth_cornerPull | 0.80 | 0.794 |
| CTRL_L_eye_blink | 0.50 | 0.499 |
| CTRL_R_eye_blink | 0.50 | 0.500 |
| CTRL_L_brow_raiseIn | 0.70 | 0.697 |
Sure: 6.6 sn (300 + 150 iterasyon, CPU). Offline cozum icin uygun; gercek zaman icin iterasyon sayisi dusurulup onceki kareden sicak baslatilmali.
Gercek MediaPipe gozlemiyle test edilmedi. Sentetik test ileri modelin kendi ciktisini kullaniyor, yani model hatasi yok; gercek veride landmark gurultusu, karsilik hatasi ve rig'in oyuncunun yuzunu tam temsil edememesi devreye girer.