- BrainTools - https://www.braintools.ru -

Фотосессия стоматологов без фотосессии за $3: FLUX.3 klein + ComfyUI + RunPod = 290 кадров

Я настраиваю платный трафик для стоматологических клиник (и не только). Для хороших креативов всегда нужны фото врачей: настоящее лицо врача в кабинете работает лучше стока. Но для клиники фотосессия врачей превращается в отдельный проект с большими временными и денежными тратами. Врачи заняты пациентами, кабинеты заняты, фотографу надо платить, а хорошему надо платить много. Через месяц нужны новые кадры, потому что старые креативы выгорели. В общем, для стоматологий это долгая и дорогая морока. Я понимал это с самого начала и решил пойти другим путём.

Фотосессия стоматологов без фотосессии за $3: FLUX.3 klein + ComfyUI + RunPod=290 кадров - 1

У клиники, обычно, есть хотя бы одна фотография каждого врача: портрет с сайта. Из такого минимума я хотел получить серию кадров: врач в лупах лечит пациента, врач у монитора с КТ, хирург на операции, детский стоматолог с игрушечным динозавром. В интерьере именно этой клиники, в её форме, с её логотипом на униформе/бейдже.

За неделю получился конвейер: ссылка на страницу врачей клиники на входе → папка проверенных кадров на выходе. Сейчас в нём несколько клиник, более 20 врачей и 290 одобренных кадров. Аренда GPU, тесты моделей и хранение заняли около $3,2.

Шесть сетапов одного врача: все кадры собраны из одного портрета с сайта клиники. Здесь и дальше лица врачей замазаны.

Шесть сетапов одного врача: все кадры собраны из одного портрета с сайта клиники. Здесь и дальше лица врачей замазаны.

Кто-то писал код, а кто-то руко-водил

Весь код и конфиги написал Claude Code Opus 5.5 (High Effort). На старте от меня потребовалось только зарегистрироваться в RunPod, баланс, API-ключ и поставить runpodctl.

Что делал я:

  • Ставил ТЗ

  • Корректировал архитектуру комбайна

  • Утверждал результат

Что делал Клод:

  • Поднимал под

  • Писал скрипты

  • Гонял генерации

  • Отсматривал каждую картинку по чек-листу

  • Останавливал под в конце сессии.

Фотосессия стоматологов без фотосессии за $3: FLUX.3 klein + ComfyUI + RunPod=290 кадров - 3

Всё управляется из CLI и YAML, а состояние проекта и решения лежат в CLAUDE.md, чтобы следующая сессия агента продолжала с того же места.

Как устроен комбайн целиком:

Человек участвует в двух местах: даёт ссылку на сайт клиники и утверждает отобранные кадры. GPU нужен только на шаге генерации.

Человек участвует в двух местах: даёт ссылку на сайт клиники и утверждает отобранные кадры. GPU нужен только на шаге генерации.

Инфраструктура: RunPod + ComfyUI через API

Требования были простые: платить только за минуты генерации, не скачивать модели заново каждый раз и не светить ComfyUI в интернет.

  • Secure Cloud + network volume на 60 ГБ. Модели и ComfyUI лежат на сетевом диске, под создаётся и удаляется вокруг него. Community Cloud дешевле, но сетевые диски не поддерживает. Диск стоит $0,07 за ГБ в месяц, то есть $4,2 в месяц, и списывается даже при остановленном поде.

  • GPU по приоритету: RTX 4090 ($0,89/ч), RTX PRO 4500 Blackwell 32 ГБ ($0,72/ч), RTX PRO 4000 Blackwell 24 ГБ ($0,57/ч). Наличие у RunPod везде «Low» и меняется за минуты, поэтому pod.sh [1] up перебирает карты по списку. Если на хосте старого пода карты нет, скрипт удаляет под и создаёт новый в том же датацентре, а диск остаётся.

  • Наружу открыт только 22/tcp. Шаблон RunPod поднимает FileBrowser и Jupyter с паролями по умолчанию, их я не публикую. ComfyUI доступен через SSH-туннель изнутри на 127.0.0.1:8188. Для пода отдельный SSH-ключ, он передаётся через переменную PUBLIC_KEY и в аккаунт RunPod не добавлен.

  • API-ключ только в .env. runpodctl вызывается через обёртку, которая читает ключ из .env. Команды runpodctl config и doctor не используются: они сохраняют ключ в ~/.runpod.

scripts/pod.sh up        # старт или пересоздание + туннель :8188
scripts/pod.sh status
scripts/pod.sh down      # закрыть туннель и остановить под

down повторяет stop, пока под не перейдёт в EXITED. Один раз API RunPod ответил таймаутом, и без повтора под остался бы включённым.

Правило проекта: перед up агент пишет примерную стоимость (тариф × время + загрузка моделей), после сессии под всегда останавливается.

Выбор модели: FLUX.2 klein 4B/9B → FLUX.3 klein

Шёл по нарастающей, на одних и тех же промптах и одном референсе.

FLUX.2 klein 4B. Сходство с одним чистым референсом хорошее. Мелкие инструменты в руке выходят лотереей: примерно в половине кадров вместо стоматологического зеркальца прут или зонд. Вместо бинокулярных луп хирург получает защитные очки из Splinter Cell. Экраны всегда резкие, с псевдоинтерфейсом и неправдоподобным рентгеном.

FLUX.2 klein 9B. Зеркальце настоящее в 3 из 3 кадров, лупы настоящие. Но лицензия FLUX Non-Commercial: коммерческой компании разрешены только тесты и оценка вне продакшена. Для рекламы нужна платная лицензия BFL, поэтому 9B осталась сравнением.

FLUX.3 Image Klein (вышла 13 октября). 5,6B трансформер, энкодер Qwen3-VL-4B, до 10 референсов, distilled-версия на 16 шагов без guidance. Из трёх моделей лучше всех следует промпту.

модель

лицензия

для рекламы

FLUX.2 klein 4B

Apache 2.0

да

FLUX.2 klein 9B, FLUX.2 dev

FLUX Non-Commercial v2.1

только тест

FLUX.3 klein

FLUX Kommunity v1.0

да, если выручка лицензиата с аффилированными лицами меньше $5M в год

У лицензии [2] FLUX.3 есть условия, которые стоит знать до продакшена.

Файлы для ComfyUI:

  • Трансформер int8 (5,8 ГБ)

  • Энкодер Qwen3-VL-4B int8 (4,9 ГБ)

  • Prompt upsampler на Qwen3.5-9B (6,7 ГБ)

  • VAE от FLUX.2.

Всего около 17,5 ГБ. ComfyUI из образа RunPod (0.30.0) про FLUX.3 не знает, пришлось обновить его на сетевом диске до 0.41.2.

Граф: upsampler обязателен

FLUX.3 ждёт полное описание сцены на 150–200 слов: кадр, человек, одежда, поза, фон, свет. Короткий промпт даёт шаблонный результат, и BFL прямо не рекомендует работать без своего prompt upsampler. Это отдельная языковая модель: она смотрит на референсы и переписывает запрос в подробное описание.

Граф собирается в Python и уходит в ComfyUI через API:

LoadImage ×N → ImageScaleToTotalPixels (1 MP) → BatchImagesNode
      │
      ├─→ TextGenerate (upsampler, системный промпт BFL для i2i, жадное декодирование)
      │        └─→ CLIPTextEncode (Qwen3-VL) ─→ ConditioningZeroOut (негатив)
      │
      └─→ ReferenceMultiLatent (resize, 1 MP) ─→ KSampler (16 шагов, cfg 1, euler/simple)
                                                  └─→ VAEDecode ─→ SaveImage

Как промпт доходит до модели, видно на одном кадре: лечение в лупах, последний в серии в начале статьи.

1. Сцена из библиотеки. Шаблон с плейсхолдерами, без помещения, формы и света (фрагмент):

prompt: >-
  {Subject} as {role} treating a patient in {room}. {He} wears {uniform}, light blue nitrile gloves,
  ... dental TTL magnifying loupes built into slim eyeglasses, exactly like the loupes in {loupes} ...
  The hands and instruments are positioned exactly as in {hands}. ... {light}.

2. Что подставил планировщик. Выбор записан в поле slots в .json рядом с кадром:

room_photo: assets/interior/treatment_2.jpg                 # → image 2
uniform: a mint-green medical tunic with a V-neck
light: soft daylight mixed with warm ceiling lights
eq_loupes: equipment/loupes/loupes_frame.jpg                 # → image 3
eq_mirror_probe: equipment/mirror_probe/dental_mirror.jpg    # → image 4
hands: hands/exam/mirror_probe_mouth.jpg                     # → image 5

На выходе промпт на 324 слова и шесть референсов: лицо, кабинет, лупы, зеркальце, хват рук, эмблема клиники.

3. Что написал upsampler (начало):

Create a photograph of the woman from the first image working as a dentist in the dental treatment
room from the second image, preserving its pale gray walls, gray tiled floor, white dental chair,
overhead examination lamp, window with beige blinds, radiator, desk, and all other furniture and
equipment. ... keeping her fair skin, long straight brown hair, and recognizable facial features.

Upsampler сократил 324 слова до 235. Он не раздувает текст, а переписывает его под модель:

  • перечислил мебель с фото кабинета;

  • сам описал внешность по фото, хотя в нашем промпте её описывать нельзя;

  • выкинул фразу про референс хвата и Documentary-style photo, 35mm lens;

  • пересчитал картинки по-своему: эмблема у него «из пятой», хотя она шестая. Кадр при этом вышел правильным.

Что важно про upsampler:

  1. Описание управляет композицией, фото управляет лицом. Чего нет в описании, модель придумает сама. Что есть на фото и не запрещено (наклон головы, одежда, надпись на стене), может перетечь в кадр.

  2. Жадное декодирование (sampling_mode: off, как в эталонном скрипте BFL): один промпт даёт одно описание. Seed меняет детали, но не позу и не композицию. Три варианта одного промпта выходят почти одинаковыми.

  3. Развёрнутое описание надо сохранять. Оно пишется в .json рядом с картинкой. Если кадр не тот, сначала читаешь описание: проблема либо в нём (правим промпт), либо в исполнении (меняем seed).

  4. Upsampler сам добавляет полезное: в других кадрах он дописывал «без логотипов и надписей на одежде» и «без вывесок».

На RTX PRO 4500 кадр считается 12–18 секунд, вместе с upsampler на каждый вариант около 27 секунд. Первый кадр сессии ждёт около двух минут, пока модели читаются с сетевого диска.

Промпты под FLUX.3: что сработало

Правила собирались из документации BFL и прогонов, все проверены на кадрах.

Не описывать внешность. Промпт начинается с The woman from image 1 as a dentist … и ни слова про возраст, причёску и бороду. Описание спорит с фото и уводит лицо.

Поворот корпуса и головы задавать отдельно. С одним референсом поза головы копируется с фото почти один в один. Помогает так:

body turned about 45 degrees to the right, arms crossed over her chest;
head turned three-quarters back toward the camera, chin level, looking into the lens

Для разных поз делать разные сетапы, а не больше вариантов. Из-за жадного upsampler варианты одного промпта однотипные.

Взгляд в камеру держит сходство лучше. Когда врач смотрит вниз в рот пациента, лицо выходит моложе и уже. Для главных креативов фронтальный или трёхчетвертной ракурс.

Medium shot framed from the waist up даёт поясной план. Просто waist-up portrait модель превращает в портрет по плечи.

Пациента одевать явно. Без a man in a casual light gray sweater модель может одеть пациента в такую же форму, как врача.

Не пытаться прятать рот пациента. Все попытки (mouth out of view, covered by his hands) уводили зеркальце к макушке, носу или глазу.

Стены без рамок. plain white walls without pictures or frames, иначе появляются рамки, похожие на дипломы. Фальшивые дипломы в рекламе врача недопустимы.

Чего писать не стоит:

не пишите

почему

8k, photorealistic, masterpiece, cinematic, commercial photography

upsampler вырезает оценочные слова, а cinematic и commercial тянут к глянцу и «пластиковой» коже

shallow depth of field, bokeh

FLUX.3 почти не размывает фон, рассчитывать на это нельзя

late 30s, short dark hair

спорит с референсом

big smile, bright white teeth

«голливудская» улыбка, по правилам клиник нельзя

diploma, name badge, имена, должности

текст и фальшивые документы в кадре

длинные списки no …, no …, no …

одно no X на конкретный риск работает, список размывает промпт

Синдром одного кабинета

Первая версия хранила готовые промпты сетапов, и врачи двух разных клиник получились почти в одинаковых кабинетах.

Фотосессия стоматологов без фотосессии за $3: FLUX.3 klein + ComfyUI + RunPod=290 кадров - 5

Пришлось разделить «что делает врач» и «где и в чём», а заодно сильно расширить число сцен и привязать их к специальностям врачей.

Теперь кадр собирается из трёх частей:

  • кто: карточка врача (doctor.yaml: специальности, пол) и фото с сайта;

  • что делает: сцена (setups.yaml): действие, поза, предмет, пациент;

  • где и в чём: облик клиники (looks.yaml): помещения, форма, свет, план кадра, логотип.

Сцены и облик лежат на двух уровнях: библиотека (library/, общая для всех клиник) и проект (projects/<клиника>/). Одно имя файла означает одно и то же на обоих уровнях, файл проекта перекрывает библиотечный: словари сливаются по ключам, списки заменяются целиком, сцены по id. Картинки (фото помещений, экраны, оборудование, хват рук) заданы папками: положил файл в assets/equipment/loupes/ или assets/screens/cbct/, запустил gen.py plan, и YAML править не надо.

Планировщик сливает библиотеку с проектом, берёт специальности из карточки врача и для каждого варианта собирает промпт и список референсов.

Планировщик сливает библиотеку с проектом, берёт специальности из карточки врача и для каждого варианта собирает промпт и список референсов.

Библиотека сцен (library/setups.yaml, 31 сцена) описывает только действие, позу, предмет и пациента. Помещение, форма, свет и план кадра подставляются плейсхолдерами:

- id: implant-in-hand
  title: "Рассматривает настоящий имплант с абатментом"
  for: [implant, surgeon]
  priority: 4
  room: surgery
  uniform: coat
  screen: cbct
  equipment: [implant]
  patient: none
  prompt: >-
    {Subject} as a dental implantologist standing in {room} beside a monitor on a stand that shows
    the 3D CBCT scan from {screen}. {He} wears {uniform} and holds between {his} thumb and index finger
    a single small titanium dental implant with its abutment exactly like the one in {implant}, real size ...

Облик клиники (projects/<клиника>/looks.yaml) перекрывает облик по умолчанию: фото помещений, палитра формы, логотип.

rooms:
  treatment:
    photos: [assets/interior/treatment_1.jpg, assets/interior/treatment_2.jpg]
    text: a bright dental treatment room with white walls, light gray floor tiles, ...
uniform:
  scrubs: [a mint-green medical tunic with a V-neck, a white medical tunic with a stand-up collar, ...]
brand:
  emblem: assets/brand/logo_emblem.png
  emblem_desc: "a white tooth inside a green circle"   # эмблема словами, пример

Планировщик (gen.py plan <клиника>) подбирает каждому врачу пять сцен по его специальностям: детский стоматолог не удаляет зубы, ортодонт не сидит в детской. Специальности он берёт по очереди, а внутри очереди сначала идут сцены, которые реже брали другие врачи этой клиники. Так у десяти врачей одной клиники не получается десять одинаковых портретов у кресла.

Для каждого варианта собирается свой промпт: форма, свет, план, деталь фона, фото помещения и картинка для монитора выбираются случайно из списков облика. Например, свет и план кадра по умолчанию:

slots:
  light:
    - soft natural daylight from a large window
    - even, bright overhead clinic lighting
    - soft daylight mixed with warm ceiling lights
    - bright clinical light with gentle shadows
  framing:
    - medium shot framed from the waist up
    - medium close-up framed from the chest up
    - three-quarter shot from the knees up

Случайность [3] воспроизводимая, генератор привязан к клинике, врачу, сцене и номеру варианта:

vr = random.Random(f"{project}|{ddir.name}|{sid}|{i}|{salt}")

Это заодно лечит однотипность из-за жадного upsampler: у трёх вариантов разные промпты, значит и разные описания. Если пятёрка врача не нравится, меняешь plan_salt в project.yaml, и планировщик перетасовывает её.

Референсы: модели нужно показывать, а не описывать

FLUX.3 klein принимает до 10 изображений. В итоге в одном кадре их бывает пять-шесть, порядок фиксирован: лицо врача, фото помещения, картинка экрана, оборудование, хват рук, логотип. Номера image N в тексте планировщик расставляет сам.

Фото помещения. Фото интерьера клиники референсом работает отлично: окно, установка, кресло, стойка ресепшена с вывеской переносятся узнаваемо, текст на вывеске правильный. Если у помещения есть фото, отдельная вывеска не добавляется, она уже на фото.

Слева фото ресепшена клиники, справа кадр, где оно подано референсом. Стойка, двери, светильники и вывеска перенесены с фото.

Слева фото ресепшена клиники, справа кадр, где оно подано референсом. Стойка, двери, светильники и вывеска перенесены с фото.

Оборудование

Без референса модель придумывает несуществующее. Бинокулярные лупы стабильно превращались в громоздкий прибор, похожий на ночное видение, на ремне вокруг головы. Словами это не лечится: «slim TTL loupes built into eyeglasses» модель читает, но рисует то, что видела чаще.

Фотосессия стоматологов без фотосессии за $3: FLUX.3 klein + ComfyUI + RunPod=290 кадров - 8

Поэтому для каждого заметного предмета в кадре есть папка с фото настоящего оборудования. Сейчас там 12 видов: лупы, микроскоп, имплант с абатментом, шкала цветов, элайнер, зеркальце и зонды, наконечники, ультразвуковой скалер, интраоральный сканер, хирургический лоток, гипсовые модели и игрушечный динозавр для детских сцен.

Часть фото с Wikimedia Commons, часть сгенерирована в Gemini. Лупы: Hs1195, CC BY-SA 4.0; зеркальце: Rachmaninoff, CC BY-SA 4.0; имплант: Coronation Dental Specialty Group, CC BY-SA 3.0; шкала: Shaimaa Abdellatif, CC BY-SA 4.0.

Часть фото с Wikimedia Commons, часть сгенерирована в Gemini. Лупы: Hs1195, CC BY-SA 4.0; зеркальце: Rachmaninoff, CC BY-SA 4.0; имплант: Coronation Dental Specialty Group, CC BY-SA 3.0; шкала: Shaimaa Abdellatif, CC BY-SA 4.0.

Откуда брал:

  • Wikimedia Commons: лупы, микроскоп, имплант, шкала, элайнеры, зеркальце, наконечники.

  • Gemini: то, что с Wikimedia взять не удалось. Скалер, сканер, лоток, гипсовые модели, динозавр.

Требования к референсу оборудования такие:

  • один предмет целиком, на нейтральном фоне, в студийном свете;

  • никаких брендов, надписей, цифр и водяных знаков: всё, что есть на референсе, переедет в кадр;

  • без людей и рук (для хвата рук отдельные референсы, о них ниже);

  • реальный размер понятен из формы: имплант длиной с фалангу пальца, а не макет размером с ладонь, который модель рисует по умолчанию.

Планировщик берёт из папки случайное фото (воспроизводимо, по seed варианта), добавляет его в список референсов и подставляет вместо {loupes} строку image 3. Какой файл ушёл в кадр, записано в .json. После подключения референсов лупы стали настоящими TTL на оправе в 6 из 6 кадров, а глаза врача видны над окулярами.

Слева референс, по центру кадр без него, справа с ним. Промпт одинаковый, лица на крупных планах замазаны.

Слева референс, по центру кадр без него, справа с ним. Промпт одинаковый, лица на крупных планах замазаны.

Экраны

Монитор без референса не работает ни на одной модели: фальшивый рентген с «фотографическими» зубами, почти читаемые подписи интерфейса, а иногда и второй экземпляр врача в кадре.

6 видов экранов: КТ в 3D и в проекциях, панорама, план имплантации, CAD коронки, интраоральный скан.

6 видов экранов: КТ в 3D и в проекциях, панорама, план имплантации, CAD коронки, интраоральный скан.

С картинкой экрана отдельным референсом сцены с монитором стали проходить: сцена со сканером дала 3 из 3. Картинки экранов я генерирую в Gemini и обрезаю полосы с читаемым текстом интерфейса.

Справа кадр FLUX.3: на мониторе тот же скан, в руке тот же сканер.

Справа кадр FLUX.3: на мониторе тот же скан, в руке тот же сканер.

Руки: самая долгая часть

Портреты прошли почти сразу. Сцены, где врач работает руками, дали больше всего брака:

  • головка зеркальца у носа пациента или в воздухе, а не во рту;

  • врач смотрит куда-то в сторону, а не в рот;

  • третья рука без перчатки на подбородке пациента;

Фотосессия стоматологов без фотосессии за $3: FLUX.3 klein + ComfyUI + RunPod=290 кадров - 13

Помогли три вещи.

Писать механику, а не «держит зеркальце рядом с пациентом».

The round head of the dental mirror is inside the open mouth, below the upper front teeth
and well below the nose, angled to reflect the inner surfaces of the teeth toward her eyes;
the tip of the probe touches one lower tooth. Her eyes look down into the patient's mouth,
at the mirror and the tooth being examined, not at the camera.

Назначать руки явно и убрать ассистента. Фраза «fingers resting on the patient’s chin» дорисовывала третью руку. Рука ассистента на краю кадра тоже путала модель. Сейчас в каждой рабочей сцене:

Her LEFT hand holds a dental mirror and her RIGHT hand holds a probe.
Exactly two hands in the frame, both her own: her LEFT hand and her RIGHT hand,
never two left or two right hands; no assistant and no other hands near the patient.

Референс хвата. Крупный план правильного хвата: две руки в перчатках, рот пациента, лицо врача обрезано. Сгенерировал его в Gemini. Промпты для Gemini пришлось переписать с теми же LEFT/RIGHT и с требованием, чтобы в кадре были видны плечи и предплечья одного человека, иначе Gemini тоже выдаёт по три руки. Если референса рук для сцены нет, планировщик сам вырезает фразу «positioned exactly as in {hands}», чтобы не ссылаться на несуществующую картинку.

Референсы хвата

Референсы хвата

Результат на одном враче: раньше зеркальце было у носа или в воздухе в половине кадров, после правок 6 из 9 кадров правильные. Остаточный брак, зеркальце у верхней губы, примерно 1 из 4 и лечится сменой seed.

Слева референс хвата, справа два варианта сетапа осмотра после правки механики: зеркальце во рту, зонд у зуба, две руки врача, взгляд в рот.

Слева референс хвата, справа два варианта сетапа осмотра после правки механики: зеркальце во рту, зонд у зуба, две руки врача, взгляд в рот.

Логотип на одежде

Логотип клиники подаётся отдельным референсом. Словами его описывать бесполезно.

  • Вывеска на стене (полный логотип с текстом) правильная примерно в 24 из 27 кадров.

  • Вышивка полного логотипа с текстом на светлой ткани правильная примерно в двух третях кадров, на тёмно-синей почти всегда кривая: буквы в названии клиники путаются и выпадают.

  • Только эмблема без текста на одежде дала 24 чистых кадра из 24 на любой ткани.

Следующая проблема: сложная эмблема упрощалась. Сейчас к эмблеме добавлено описание словами (emblem_desc), оно подставляется во фразу про вышивку.

Врачи с сайта клиники

Карточки врачей собирает универсальный парсер fetch_doctors.py: на вход ссылка на страницу врачей. ФИО он ищет по цепочке JSON-LD → H1 → og-теги, должность по классам вроде position/specialty и по русским синонимам специальностей из библиотеки. Фото собирает из <img>, srcset, data-src, CSS-фонов и og:image, каждому ставит оценку и штрафует сертификаты, дипломы, логотипы и слайдеры. Страницу списка определяет по ссылкам с ФИО.

Должность с сайта превращается в специальности по русским синонимам из библиотеки:

# library/setups.yaml
specialties:          # role идёт в промпт, ru ищет парсер на сайте
  general: {role: "a dentist", ru: [стоматолог общей практики, врач-стоматолог, стоматолог, зубной врач]}
  chief:   {role: "the head of the dental clinic", ru: [главный врач, главврач, руководитель клиники, директор]}
# doctors/<врач>/doctor.yaml
title_site: Директор стоматологической клиники, врач стоматолог общей практики.
specialties: [general, chief]
gender: female

Из-за chief в пятёрку этого врача попали сцены руководителя: за рабочим столом и у стойки ресепшена (первые два кадра в серии в начале статьи).

У второй клиники раздела врачей не было вовсе: семь врачей нашлись в карусели на главной, без ссылок на отдельные страницы. Парсер научился режиму «карточки без ссылок» и перестал жёстко штрафовать слайдер, если в alt есть фамилия.

После парсера фото всё равно смотрит человек: обрезает логотипы на стене и бейджи с ФИО, проверяет пол. Надписи с референса переносятся в результат в искажённом виде.

Проверка: чек-лист на каждый кадр

Каждую картинку агент смотрит по семи пунктам:

  1. Сходство лица с референсом.

  2. Зубы и улыбка: нормальное количество, естественный цвет, без «голливуда».

  3. Руки, перчатки, пальцы.

  4. Правдоподобие оборудования и инструментов.

  5. Кожа без «пластика».

  6. Механика работы: зеркальце во рту, зонд у зуба, ровно две руки врача (одна левая, одна правая), взгляд в рот.

  7. Текст и логотипы: нет читаемого текста, на одежде только логотип клиники или ничего, без дипломов, ФИО, должностей и «до/после».

Неудачный кадр перегенерируется с другим seed. Промпт меняется, только если проблема явно в нём, и тогда я вижу diff. Для проверки скрипт собирает контакт-листы: один лист на врача, строки это сетапы, столбцы это варианты.

Результаты зависят от сцены. Вторая клиника, портреты и консультации: одобрено 103 из 105 кадров с первого раза. Первая клиника, 9 врачей × 5 сетапов × 3 варианта плюс перегенерация 8 сетапов: 120 кадров в одобренных, у каждого врача от 12 до 15. Последний тест рабочих сцен с новыми референсами: 11 из 15 одобренных.

Сколько это стоит

  • Пакет на 9 врачей (135 кадров плюс перегенерация): под работал около 57 минут на RTX PRO 4500, около $0,68.

  • Пакет на 7 врачей (105 кадров): около 35 минут, около $0,42.

  • За всё время, включая сравнение трёх моделей, отладку и хранение диска: около $3,2 из стартовых $10.

  • Сетевой диск: $4,2 в месяц, пока он существует.

Фотосессия стоматологов без фотосессии за $3: FLUX.3 klein + ComfyUI + RunPod=290 кадров - 16

Получается порядка цента за одобренный кадр. Дороже обходится время человека на просмотр и отбор.

Что не получилось

  • Размытый фон. FLUX.3 почти не слушается shallow depth of field, фон резкий и иногда с HDR-оттенком «рендера».

  • Однотипность при одном фото. С одним референсом поза головы и выражение тянутся с фото. 2–3 фото врача в разных ракурсах дали бы заметно больше разнообразия, но у клиник обычно одно.

  • Мелкая вышивка. Эмблема со сложной формой иногда упрощается, текст на ткани выходит только на светлом.

  • Взгляд вниз. Сходство в рабочих сценах слабее, чем в портретах.

Итог

Весь конвейер сейчас это около 1300 строк Python, 170 строк bash и 700 строк YAML, из которых 500 это тексты сцен:

python3 fetch_doctors.py Clinic https://example-clinic.ru/doctors/   # врачи с сайта
python3 gen.py plan Clinic                                           # пятёрки сетапов по врачам
scripts/pod.sh up
python3 gen.py batch Clinic                                          # генерация
python3 scripts/contact_sheet.py projects/Clinic/out/2026-10-20      # листы для просмотра
python3 gen.py approve projects/Clinic/out/2026-10-20/<кадр>.png     # в одобренные
scripts/pod.sh down

Главный вывод для меня:

  1. В задаче «реальный человек в реальной обстановке» текстовый промпт решает меньше, чем кажется.

  2. Работают референсы: фото помещения, фото настоящего оборудования, картинка экрана, хват рук, логотип отдельным файлом. А промпт описывает механику: где рука, где зеркальце, куда смотрит врач.

Если делаете похожее, пишите в комментариях, какие сцены у вас не получаются.

Автор: cpa_jedi

Источник [4]


Сайт-источник BrainTools: https://www.braintools.ru

Путь до страницы источника: https://www.braintools.ru/article/37806

URLs in this post:

[1] pod.sh: http://pod.sh

[2] лицензии: https://huggingface.co/black-forest-labs/flux-3-image-klein/blob/main/LICENSE.md

[3] Случайность: http://www.braintools.ru/article/6560

[4] Источник: https://habr.com/ru/articles/1101804/?utm_campaign=1101804&utm_source=habrahabr&utm_medium=rss

www.BrainTools.ru

Rambler's Top100