Skip to content

Commit c8a0b3e

Browse files
author
dimitrivoytan
committed
add psp project
1 parent 9bc37d5 commit c8a0b3e

File tree

5 files changed

+59
-53
lines changed

5 files changed

+59
-53
lines changed

_projects/portrait_studio_pro.md

Lines changed: 59 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,87 @@
11
---
22
layout: page
33
title: Portrait Studio Pro
4-
description: Generate professional headshots by fine tuning a flow-based generative model.
4+
description: Behind the scenes of my full stack ML application.
55
img: assets/img/projects/portrait_studio_pro/realtor.png
66
importance: 1
77
category: fun
88
related_publications: false
99
---
1010

11-
<!-- Every project has a beautiful feature showcase page.
12-
It's easy to include images in a flexible 3-column grid format.
13-
Make your photos 1/3, 2/3, or full width.
11+
<image/>
1412

15-
To give your project a background in the portfolio page, just add the img tag to the front matter like so:
13+
## Introduction
1614

17-
---
18-
layout: page
19-
title: project
20-
description: a project with a background image
21-
img: /assets/img/12.jpg
22-
---
15+
If you’re chasing a polished LinkedIn photo but can’t spare an afternoon at a studio, AI headshots feel almost magical. The premise is straightforward: fine-tune a diffusion model on a small batch of your selfies, then let the network re-imagine you under flattering lights, crisp backdrops, and perfectly-pressed suits. With [Portrait Studio Pro](https://www.proaiheadshot.com), I turned that idea into a full-fledged web application that delivers more than 120 portraits in under two hours.
16+
17+
My own journey to that point was hardly instant. I cycled through half-a-dozen model architectures, hosting providers, and user flows before the pieces finally clicked. Along the way, competitors like Danny Postma’s HeadshotPro, Levels.io’s PhotoAI, and venture-backed Aragorn AI appeared on the radar. Far from discouraging me, their presence signaled genuine demand—“friendly competition” that forced better engineering decisions week after week.
2318

2419
<div class="row">
2520
<div class="col-sm mt-3 mt-md-0">
26-
{% include figure.liquid loading="eager" path="assets/img/1.jpg" title="example image" class="img-fluid rounded z-depth-1" %}
27-
</div>
28-
<div class="col-sm mt-3 mt-md-0">
29-
{% include figure.liquid loading="eager" path="assets/img/3.jpg" title="example image" class="img-fluid rounded z-depth-1" %}
30-
</div>
31-
<div class="col-sm mt-3 mt-md-0">
32-
{% include figure.liquid loading="eager" path="assets/img/5.jpg" title="example image" class="img-fluid rounded z-depth-1" %}
21+
<div class="w-50 mx-auto">
22+
{% include figure.liquid loading="eager" path="assets/img/projects/portrait_studio_pro/hero_pic.webp" title="example image" class="img-fluid rounded z-depth-1" %}
23+
</div>
3324
</div>
3425
</div>
3526
<div class="caption">
36-
Caption photos easily. On the left, a road goes through a tunnel. Middle, leaves artistically fall in a hipster photoshoot. Right, in another hipster photoshoot, a lumberjack grasps a handful of pine needles.
27+
Examples of headshots generated from the app (first version)
3728
</div>
29+
30+
## Building the Stack
31+
32+
I started by building out front end. I prototyped a layout in Figma and then coded it up with Next.js. I chose the App Router version of Next.js, which renders pages on the server by default. Client-side interactivity is opt-in: at the top of a file you add `use client` string to make React hooks like `useState` behave as expected. It felt a little quirky at first, but I adapted. At the time, the App Router version was brand new, so AI models were very unhelpful, having a tendency to use Page Router schema. The first version of the frontend took a couple of evenings to flesh out.
33+
34+
The backend is entirely Firebase. User profiles and order metadata sit in Cloud Firestore, while raw selfies and generated headshots rest in Cloud Storage. A fleet of TypeScript Cloud Functions orchestrates the rest: Stripe checkout, webhook processing, email reminders, and the heavy-duty job queue that triggers model training and inference. SendGrid handles the mailouts.
35+
36+
The machine-learning pipeline was the most comfortable piece for me to build. At a high level, every uploaded image passes through a pipeline: face detection, rotation correction, gender detection, brightness and blur checks, and a light histogram adjustment to even out exposure. Once an image set clears quality control, I do a LoRA fine-tuning of a flow-based generative model. Inspired by the DreamBooth recipe, the model learns a new token e.g. “photo of ohwx man”while staying anchored by generic regularization prompts and images such as “photo of a man.”
37+
38+
To tune hyperparameters of the model, I build a dataset of people including my own face, my wife’s, obliging friends, even a few public-domain celebrities. Early models used convolution-based architectures based on Stable Diffusion. I found that LoRA of these models led to blurrier and smoother photos than full fine tuning, so these models did a full-parameter fine tune. The hyperparameters were a bit challenging, because it was easy for the model to collapse under all but very modest learning rates. Later models, with a transformer-based architecture, were fine with LoRa and produced better images. After running experiments, I found a LoRA rank, learning-rate schedule, and number of training steps that produced reasonably consistent result across the dataset. I didn't use any specific metrics, I just ranked outputs by my personal preference.
39+
40+
For Stable-Diffusion models, prompt engineering was really important, for example, positive prompts read:
41+
42+
```
43+
professional headshot of ohwx man, standing in a modern office lobby, wearing a black suit and white shirt, 4K UHD, bokeh lighting, high detail, sharp focus
44+
```
45+
and negative prompts:
46+
47+
```
48+
lowres, bad anatomy, bad hands, signature, watermarks, ugly, imperfect eyes, skewed eyes, unnatural face, unnatural body, error, extra limb, missing limbs
49+
```
50+
51+
On the other hand, Transformer based models worked really well without extensive prompt engineering
52+
53+
To serve the inference pipeline, I started by using google cloud batch, but quickly ran into a lot of issues. For example, my GPU quota was set to 1 and I couldn't get approval for more than one concurrent GPU, so jobs backed up in the queue and were timing out. The retry mechanism then introduced some bug I never figured out; it provisioned disk-usage quota on each retry (even before the vm instance launched) and I ran into a Zone Resources Exhausted problem (due to the growing disk quota that was never really used). I gave up trying to figure this out and switched to containerized model for deployment on Replicate via the Cog toolkit. Replicate offers serverless GPUs inference, and cog made it fairly straightforward. I did have to crack open the generated Dockerfile to manually tweak some `c` libraries, but once that hurdle was cleared, everything flowed with almost no friction.
54+
3855
<div class="row">
3956
<div class="col-sm mt-3 mt-md-0">
40-
{% include figure.liquid loading="eager" path="assets/img/5.jpg" title="example image" class="img-fluid rounded z-depth-1" %}
57+
{% include figure.liquid loading="eager" path="assets/img/projects/portrait_studio_pro/downtown.jpeg" title="Downtown Style" class="img-fluid rounded z-depth-1" %}
4158
</div>
42-
</div>
43-
<div class="caption">
44-
This image can also have a caption. It's like magic.
45-
</div>
46-
47-
You can also put regular text between your rows of images, even citations {% cite einstein1950meaning %}.
48-
Say you wanted to write a bit about your project before you posted the rest of the images.
49-
You describe how you toiled, sweated, _bled_ for your project, and then... you reveal its glory in the next row of images.
50-
51-
<div class="row justify-content-sm-center">
52-
<div class="col-sm-8 mt-3 mt-md-0">
53-
{% include figure.liquid path="assets/img/6.jpg" title="example image" class="img-fluid rounded z-depth-1" %}
59+
<div class="col-sm mt-3 mt-md-0">
60+
{% include figure.liquid loading="eager" path="assets/img/projects/portrait_studio_pro/blue.jpeg" title="Blue Style" class="img-fluid rounded z-depth-1" %}
5461
</div>
55-
<div class="col-sm-4 mt-3 mt-md-0">
56-
{% include figure.liquid path="assets/img/11.jpg" title="example image" class="img-fluid rounded z-depth-1" %}
62+
<div class="col-sm mt-3 mt-md-0">
63+
{% include figure.liquid loading="eager" path="assets/img/projects/portrait_studio_pro/woods.jpeg" title="Woods Style" class="img-fluid rounded z-depth-1" %}
5764
</div>
5865
</div>
5966
<div class="caption">
60-
You can also have artistically styled 2/3 + 1/3 images, like these.
67+
Examples of various styles available to select from.
6168
</div>
6269

63-
The code is simple.
64-
Just wrap your images with `<div class="col-sm">` and place them inside `<div class="row">` (read more about the <a href="https://getbootstrap.com/docs/4.4/layout/grid/">Bootstrap Grid</a> system).
65-
To make images responsive, add `img-fluid` class to each; for rounded corners and shadows use `rounded` and `z-depth-1` classes.
66-
Here's the code for the last row of images above:
67-
68-
{% raw %}
69-
70-
```html
71-
<div class="row justify-content-sm-center">
72-
<div class="col-sm-8 mt-3 mt-md-0">
73-
{% include figure.liquid path="assets/img/6.jpg" title="example image" class="img-fluid rounded z-depth-1" %}
74-
</div>
75-
<div class="col-sm-4 mt-3 mt-md-0">
76-
{% include figure.liquid path="assets/img/11.jpg" title="example image" class="img-fluid rounded z-depth-1" %}
77-
</div>
78-
</div>
79-
```
70+
## Launching
71+
72+
I learned quickly just how hard and time-consuming marketing is, so I kept this part short. For SEO, I did basic keyword research and created handful of geo-targeted landing pages ( e.g. “Professional Headshots Houston” for several locales in the US and Canada). After some time this led to some organic trickle. A brief Google Ads experiment converted at roughly three percent. This was solid until cost-per-click ballooned as the niche heated up. Surprisingly, the best traffic came from a paid listing on an AI-tools directory, where visitors arrived already “bottom-of-funnel.”
73+
74+
Google Analytics tracked every step from upload to checkout, while a modest email drip coaxed fence-sitters back with escalating discounts. Those three touches—after one hour, one day, and two days—turned enough “maybes” into paying customers.
75+
76+
## Lessons Learned
77+
I learned alot about frontend development with React, Next.js and tailwind.css. I learned a fair bit about cloud computing. Firebase proved an ideal launch-pad: no servers to babysit, no manual TLS, no schema migrations—just ship. Over the long haul, the convenience tax may push core services onto cheaper or faster rails, but for version one it was perfect.
78+
79+
On the ML side, I learned about DreamBooth-style fine-tuning and LoRA. These are useful when data is scarce. A dozen well-lit selfies are genuinely enough to anchor identity, provided you guard your learning rate. A lightweight five-star ranking UI trumped any attempt at automated metrics; sometimes “vibe-checking” really is the best-available science.
80+
81+
82+
## Closing Thoughts
83+
84+
From a handful of smartphone selfies to a gallery of studio-quality portraits, [Portrait Studio Pro](https://www.proaiheadshot.com) blends fast ML with no-nonsense product design. The stack will evolve—edge inference, instant previews, maybe even short video captures—but the current system already delights customers and pays its way.
85+
86+
If you’re eyeing a similar project, focus on rock-solid preprocessing, treat prompt engineering as a first-class citizen, and never underestimate the power of a single well-timed email. The technology is cutting-edge; the fundamentals of good user experience are as old as photography itself.
8087

81-
{% endraw %} -->
319 KB
Loading
260 KB
Loading
1.24 MB
Loading
259 KB
Loading

0 commit comments

Comments
 (0)