Головна / Блог / Генератор виконує напрямок, але не величину: 48 кадрів

Генератор виконує напрямок, але не величину: 48 кадрів

Я згенерував 48 кадрів свого обличчя в ChatGPT. У продакшн пішло 11 — разом 46 кілобайт.

На головній сторінці kishkin.dev стоїть моя фотографія в креслярській рамці. Донедавна це була просто фотографія. Тепер аватар стежить за курсором, моргає — і всміхається, коли ви тягнетеся до кнопки. Одне фото з телефону важить у сто разів більше за весь ефект.

Дорогою я побудував і викинув сім напрямків — тривимірну сітку, карту глибини, оптичний потік. І вперся в межу, якої не обійти: генератор виконує «подивись убік», але не виконує «наскільки». Нижче — виміряна цифра під кожним із цих рішень.

Коротко. Портрет на головній стежить за курсором, моргає і всміхається, а коштує сторінці 7 398 байтів у двох запитах на завантаженні; решта 38 930 приходить, тільки коли рушила миша. Дорогою я побудував і викинув сім напрямків: тривимірну сітку, карту глибини, оптичний потік, рухоме світло, плавний перехід між поглядами. Головне, за що я заплатив двома днями: генератор виконує «подивись убік», але не виконує «наскільки» — на дослівно однаковому промпті амплітуда погляду розсипалася від 94 до 130 % усередині однієї пачки.

АРКУШ · АВТОПОРТРЕТ

REF DK-01

Dmytro Kishkin
ІМ’ЯD. KISHKIN
РОЛЬTECH LEAD
REFDK-01

Це не знімок. Це той самий блок, що стоїть на головній — наведіть курсор.


Скільки це коштувало: 33 промпти, 48 кадрів, 46 КБ

Ефект коштував 33 написані промпти і 48 згенерованих кадрів, з яких у продакшн пішло 11. Усе згенеровано в ChatGPT images 2.0 — це важливо, бо все, що нижче про амплітуду погляду, стосується саме цього рушія й саме цієї версії. Разом вони важать 46 328 байтів. Кожен кадр — це моє обличчя, згенероване з однієї реальної фотографії: та сама людина, той самий ракурс, змінено тільки очі або тільки рот.

Показник Значення
Написано промптів 33
Згенеровано кадрів (ChatGPT images 2.0) 48
Пішло в продакшн 11
Важить усе разом 46 328 байтів

З 48 спроб вижили вісім напрямків погляду, два положення повік і одна усмішка.

Решта 37 — це і є робота. Не чернетки, а перевірені й закриті гіпотези.


Хибний шлях: півтора тижня на перетікання фото в маскот

Одинадцять прототипів поспіль розбилися об одну цифру, яку я виміряв на самому початку. До того плита мала два стани: фотографія і мальований маскот, і портрет мав «перетікати» в ілюстрацію.

Голова маскота на 7 % ширша за мою на фото, а плечі — на 10 % вужчі. Жоден трансформ не збігається з обома одночасно.

Далі був список винаходів, щоб цю розбіжність сховати: переворот плитки, розмиття, виносні підписи, підміна фону, стос аркушів, віконце-лупа, WebGL, фліп картки.

Патерн скрізь однаковий:

Зусилля йшли на приховування невідповідності. А приховування завжди читається дешево.

Розворот був простий і безкоштовний. Не робити другу картинку. Взяти одну фотографію і змусити її реагувати. Ховати нічого — бо ховати нема чого.

Розворот того ж роду я вже робив із зовнішнім диском: відмовився від NTFS-драйвера в macOS замість того, щоб шукати надійніший.

Сім підходів, які я побудував і викинув: 3D-сітка, глибина, оптичний потік

Кожен із семи був живим прототипом, не ідеєю на серветці. Тривимірна сітка на 16 тисяч точок працювала і виявилася надмірною. Карта глибини дала 5,29 пікселя руху на голові, яка на екрані має 102. Оптичний потік знайшов 0,2 пікселя там, де око пройшло 10. Замість усього цього лишився паралакс на чистому CSS.

Фотосесія зі штативом. Технічно найправильніший шлях: 20 хвилин, штатив, серія кадрів «очі вліво / вправо / вгору / вниз». Я навіть розписав собі порядок кадрів — він досі лежить у репозиторії. Відкинуто, бо я не зніму себе добре — і це нормальна причина.

Тривимірна модель обличчя. Three.js, справжня сітка з 16 тисяч точок. Прототип працював. Відкинуто як надмірне.

Карта глибини. Найдорожче з викинутого: я побудував її повністю, з окремим стендом, щоб побачити ефект у русі. Порахував чесно: 5,29 пікселя руху «ніс проти вуха» на голові, яка на екрані має 102 пікселі. За ці п’ять пікселів довелося б платити WebGL, запасним шляхом і перекалібруванням щоразу, коли зміниться фотографія.

Рухоме світло. Виглядало, ніби на обличчя світять ліхтариком. Відкинуто на око за десять секунд.

Плавний перехід між поглядами. Здається очевидним — і не працює в принципі. Між «дивиться прямо» і «дивиться вбік» немає проміжного кадру. Якщо змішати два кадри навпіл, темна райдужка одного лягає на біле білко іншого, і око стає сірим, як від катаракти.

Оптичний потік. Друга спроба зробити проміжний кадр, математично. Алгоритм знайшов 0,2 пікселя руху там, де око пройшло 10: темна райдужка, що їде по гладенькому білку, для нього просто невидима.

Проміжна амплітуда з генератора. Про це — окремо, нижче.

Замість глибини лишився чистий CSS: стіна позаду рухається на 25 пікселів, портрет — на 5. Двадцять пікселів паралаксу за нуль нових файлів.


Чи можна задати ChatGPT точний кут погляду?

Ні. Генератор виконує «подивись убік», але не виконує «наскільки». Я просив половину від максимуму п’ятьма різними формулюваннями: «близько 18 градусів» дало погляд до упору, «на чверть ширини райдужки» — 81 %, координати в сантиметрах і пікселях — 117 і 124 %. Формулювання, яке влучило в 74 %, на повторі дало 111 % із розкидом від 94 до 130 % — усередині однієї пачки, на дослівно однаковому тексті.

Як просив Що прийшло
«близько 18 градусів» до упору
«на чверть ширини райдужки» 81 % від максимуму
точка за 15 см від об’єктива + зсув у пікселях 117 %
те саме, з ширшого кропу 124 % — і почало перемальовувати волосся й щелепу
«він дивиться у ваше друге око» 74 % — влучив!

Останнє схоже було на рецепт. Я розписав його на всі вісім напрямків і запустив пачкою — і отримав 111 %, з розкидом від 94 до 130 %, усередині однієї пачки, на дослівно однаковому формулюванні.

Тобто 74 % були щасливим жеребом усередині власного розкиду моделі, а не відповіддю на мої слова.

Висновок, за який я заплатив двома днями: напрямок генератор віддає, дозування — ні. Питання закрите, обхідного шляху немає.


Як це працює: спрайт очей 201 × 114 поверх одного фото

Тримати одинадцять повних версій фотографії коштувало б 360 кілобайт і 25 мегабайт пам’яті браузера — заради ефекту в аватарці. Замість цього кожен кадр обрізаний до самих очей: прямокутник 201 × 114 пікселів, який лягає поверх основного фото, як наліпка. Обличчя, волосся, сорочка і стіна завантажуються один раз. Рот — друга, незалежна наліпка на 6,8 КБ.

Fig. 01 · Компас погляду — вісім кадрів і спокій kishkin.dev
  • Очі зведені вгору й ліворуч: райдужки в зовнішніх верхніх кутах, під ними видно білки

    Вгору-ліворуч 216°

  • Очі підняті вгору майже без бічного зсуву: райдужки під верхніми повіками

    Вгору 274°

  • Очі зведені вгору й праворуч: райдужки біля верхніх правих країв повік

    Вгору-праворуч 332°

  • Погляд суворо ліворуч: обидві райдужки притиснуті до лівих кутів очей, білки відкриті справа

    Ліворуч 179°

  • Базовий кадр: очі дивляться просто в камеру, райдужки посеред повік

    Спокій —

  • Погляд суворо праворуч: обидві райдужки біля правих кутів очей, білки відкриті зліва

    Праворуч 354°

  • Очі опущені вниз і ліворуч: райдужки в нижніх лівих кутах, верхні повіки трохи насунуті

    Вниз-ліворуч 139°

  • Очі опущені просто вниз: райдужки біля нижніх повік, погляд нижче лінії камери

    Вниз 90°

  • Очі опущені вниз і праворуч: райдужки в нижніх правих кутах повік

    Вниз-праворуч 47°

Кут під кожним кадром — не той, що просили в промпті, а виміряний по зміщенню райдужки на вже зібраному ассеті. Тому компас шукає найближчий із цих дев’яти справжніх станів, а не ділить коло на рівні сектори.

Це не рівний восьмипроменевий компас, хоч я просив саме такий. Діагоналі завалені до горизонталі: на «вгору й ліворуч» генератор віддає здебільшого «ліворуч». Я зміряв усі 45 згенерованих кадрів і назвав їх удруге, незалежно від першого разу — обидва рази збіглися. Вибір найкращих із цих 45 замість перших-ліпших збив середню похибку з 7,2° до 4,3°. Один напрямок, «вгору-праворуч», так і лишився з похибкою 15°: у наборі просто немає кадру, який його обслуговує.

Fig. 02 · Повіки й усмішка — окремі шари kishkin.dev
  • Портрет у спокої: очі розплющені, рот у базовому положенні

    Спокій —

    Базова плита. Жодної латки зверху.

  • Портрет із напівприкритими повіками — середній кадр моргання

    Напівприкриті 86 / 88 %

    Апертура повік. Тільки в спокої.

  • Портрет із заплющеними очима — нижній кадр моргання

    Заплющені 66 / 62 %

    Апертура повік. Без підморгування.

  • Портрет усміхається: ширша усмішка зі збереженою щілиною між передніми зубами

    Усмішка 6,8 КБ

    Друга латка зі своєю маскою.

Очі й рот — два незалежні шари над двома незалежними ділянками, тому портрет усміхається, не перестаючи стежити за курсором.

У цьому й суть розділення на два шари: стеження саме по собі — це увага, а стеження плюс усмішка — це вже вітання.

Три випадки, коли метрика сказала «так», а око — «ні»

Автоматична перевірка тричі обрала кадр, який на екрані виглядав неправильно. Вона плутала опускання повіки з рухом зіниці, тому кадр із примруженим оком отримував ідеальний кут. Смуга прокрутки шириною 15 пікселів ламала погляд тільки праворуч. А шов через усе обличчя знайшли рецензенти, не я.

Чоловік, який щурився. Автоматична перевірка обрала кадр «погляд праворуч» за ідеальним кутом — 1 градус від ідеалу. На екрані це був чоловік, який мружиться і ледь дивиться вбік. Виявилося, перевірка прийняла опускання повіки за рух зіниці й подарувала кадру хід, якого не було. Я тричі відкидав цей кадр на око, перш ніж додумався, що міряти треба іншим методом.

Погляд, що зривався тільки праворуч. Симптом був конкретний: погляд повертався в центр щоразу, коли я вів курсор праворуч. Причина — смуга прокрутки. Сторінка на 15 пікселів вужча за вікно, курсор перетинає цю смужку, браузер вирішує, що миша пішла зі сторінки, і очі повертаються в центр. Ліворуч, згори і знизу такої смужки немає — дефект міг з’явитися тільки праворуч.

Шов, якого я не бачив, і чужі зуби. Я зібрав усмішку і перевірив її, збільшивши втричі. Чисто. Тоді дав чотирьом рецензентам наосліп, не сказавши, що є що. Троє знайшли те саме: жорсткий шов через усе обличчя на рівні ніздрів — він ішов уздовж природної тіні під носом, тому я його й не помітив. А ще вони знайшли те, чого не бачив ніхто два дні: у попередньому варіанті усмішки були чужі зуби — рівні, біліші, без моєї справжньої щілини.

Метрика сказала «так», око сказало «ні». Око праве.

Скільки важить на завантаженні: 7,4 КБ замість 46

На завантаженні сторінка бере 7 398 байтів у двох запитах замість 46 328 в одинадцяти. Решта — 38 930 байтів у дев’яти запитах — приходить лише тоді, коли рушила миша. Портрет моргає незалежно від того, чи прийшов курсор, але вісім кадрів погляду мертві, доки миша не рушить, а усмішка — доки хтось не потягнеться до посилання.

На завантаженні Потім, за потреби
Було 46 328 байтів, 11 запитів —
Стало 7 398 байтів, 2 запити 38 930 байтів, 9 запитів

Відвідувач із клавіатури не тягне жодного кадру погляду. Телефон бере тільки пару для моргання. Хто ввімкнув «зменшити рух» (prefers-reduced-motion) — не бере нічого.

Перше моргання: 0,7 секунди замість 5,6

Перше моргання бралося з того самого генератора випадкових пауз, що й усі решта — від 2,6 до 6,8 секунди. Тобто чи побачить відвідувач головний ефект узагалі — вирішував кидок кубика в той єдиний момент, коли це важило найбільше.

Останнє, що лишалося, я сформулював собі одним реченням: моргання має статися, поки людина ще дивиться на портрет, а не коли вона вже гортає далі.

Виміряв на продакшні: кадри готові на 0,5 секунди, сторінка догружається на 0,8, а перше моргання падало на 5,6 секунди. До того часу людина вже прочитала заголовок і пішла.

Тепер — 0,7 секунди після появи повік. Один раз, не випадково. Далі звичайний ритм.


П’ять висновків, які переносяться на інші задачі

1. Роботою було відсіювання, а не збірка. Відсіювалося й «як», і «що». Я хотів ефект, який помітять, і не був певен, що зроблю такий сам, — тому спершу шукав готові бібліотеки, приклади, плавні переходи одного зображення в інше. Сім напрямків побудовано й викинуто, 37 кадрів із 48 не пішли нікуди. А задумане виявилося простішим і легшим за будь-яке готове рішення: 46 кілобайт, без WebGL, без перекалібрування — і коштувало воно рівно стільки, скільки коштувало закрити решту. Так само я рахую вартість рішень і на більшому масштабі — в огляді проєктів ця сама арифметика записана в тисячах сайтів і мільйонах інсталяцій.

2. Дивіться в тому масштабі, у якому це побачить людина. Плита на екрані — 358 пікселів, око — 22. Дефект при триразовому збільшенні й дефект у житті — це два різні списки.

3. Шукайте край, де середовище влаштоване інакше. Погляд зривався тільки праворуч — не тому, що правий бік важчий, а тому, що це єдиний край зі смугою прокрутки: сторінка на 15 пікселів вужча за вікно. Ліворуч, згори і знизу такої смужки немає, тож більше ніде цей дефект і не міг з’явитися.

4. Стороння сліпа перевірка бачить те, чого не бачить автор. Чотири рецензенти, які не знали, що є що, за п’ять хвилин знайшли шов, який я особисто перевіряв і пропустив. Це частина того, як я працюю, а не разовий захід.

5. Генератор виконує «подивись убік», але не виконує «наскільки». Плануйте роботу так, щоб «трохи» і «наполовину» вам не знадобилися.

Що ще не зроблено: нижні повіки в усмішці

Коли людина по-справжньому сміється, у неї піднімаються нижні повіки. У мого портрета вони лишаються спокійними над піднятими щоками. Полагодити це можна тільки перегенерувавши всі вісім кадрів погляду в усміхненому стані — і знову грати в лотерею амплітуди.

Портрет живий. Він просто ще не сміється по-справжньому.


Решта інженерних розборів — у блозі; якщо хочете обговорити щось із цього, напишіть мені.

FIG. ✕ — СПІВПРАЦЯ

Шукаєте senior engineering leadership?

Архітектурний напрям, performance engineering і senior-технічна відповідальність для команд, яким це потрібно. Обговоримо, чи підходимо одне одному.

— EOF

MANIFEST · ENGAGEMENT.cfg

ВідкритийДоСпівпраці

Предметна розмова про senior engineering leadership, складні системні задачі або вбудовану консультаційну співпрацю.

Локація
Віддалено
Відповідь
< 24h
Формати
Консультації / lead-ролі
Обговорити співпрацю