Outdoor portrait generator: a quality and speed test A developer built a small Pillow and OpenCV script for evaluating outdoor portrait generators, finding that whole-frame Laplacian variance misleads because blurred backgrounds dominate the image. On a calibration daylight portrait, the whole frame scored 106.0 — barely above the conventional 100 blur threshold — while the face crop scored 605.2 and the blurred background just 6.9, so the script reports a face crop separately. The author notes no generated photos were actually ordered, so the results are not a product score. To test an outdoor portrait generator, measure sharpness on a crop of the face and time the order from upload to the "photos ready" email. A whole-image sharpness score misleads because a good outdoor portrait is mostly blurred background. The generator I would run this on first is PFPMaker https://pfpmaker.ai , which promises delivery in under 10 minutes. I should say up front that I have not ordered generated photos for this article, so nothing below is a score for any product. I wrote a small Pillow and OpenCV check and calibrated it on one real daylight portrait. The calibration exposed a mistake in the obvious way of scoring, and I think that mistake is more useful to a developer than another gallery of samples judged by eye. I expected the standard blur check to be a fair first pass. It is the variance of the Laplacian on a grayscale image, which is one line of OpenCV. Adrian Rosebrock's 2015 write-up of the method https://pyimagesearch.com/2015/09/07/blur-detection-with-opencv/ uses 100 as the default threshold and calls anything lower blurry. He also warns that the number is "quite domain dependent". The calibration photo was a free stock portrait cropped to 1200 x 675, a man in a white T-shirt standing in front of out-of-focus trees with round bokeh highlights. It is sharp where it should be. The chain around his neck is crisp. | Region | Size in pixels | Laplacian variance | |---|---|---| | Whole frame | 1200 x 675 | 106.0 | | Face crop | 220 x 280 | 605.2 | | Blurred background, top right | 350 x 300 | 6.9 | The whole frame scored 106.0, which clears the threshold by six points. The face scored 605.2 and the background 6.9, and the background is most of the picture. A batch script with a single cut-off at 100 came within six points of rejecting a photo whose face is six times over the line. I think this matters more for outdoor portraits than for studio headshots, because shallow depth of field against trees is the look people are paying for. The better a generator imitates a fast lens, the worse its files do on a whole-frame score. So the script takes a crop box and reports the face separately. It prints the pixel size, the per-channel means, a red-to-blue ratio and the Laplacian variance, first for the whole frame and then for the face box you pass in. The channel means come from Pillow's ImageStat module https://pillow.readthedocs.io/en/stable/reference/ImageStat.html . python3 -m pip install --upgrade Pillow python3 -m pip install opencv-python numpy On a server or in Docker, the opencv-python package page https://pypi.org/project/opencv-python/ says to install opencv-python-headless instead, and to install only one of the two because they share the cv2 namespace. Save this as portrait check.py : python import sys import cv2 import numpy as np from PIL import Image, ImageStat def measure pil image : means = ImageStat.Stat pil image .mean red blue = means 0 / means 2 if means 2 else float "nan" gray = cv2.cvtColor np.asarray pil image , cv2.COLOR RGB2GRAY sharpness = float cv2.Laplacian gray, cv2.CV 64F .var return means, red blue, sharpness def report label, pil image : means, red blue, sharpness = measure pil image print f"{label} means: R {means 0 :.1f}, G {means 1 :.1f}, B {means 2 :.1f}" print f"{label} R/B ratio: {red blue:.2f}" print f"{label} Laplacian variance: {sharpness:.1f}" if len sys.argv = 6: raise SystemExit "Usage: python portrait check.py IMAGE LEFT TOP RIGHT BOTTOM" image = Image.open sys.argv 1 .convert "RGB" box = tuple int value for value in sys.argv 2:6 print f"size: {image.size 0 } x {image.size 1 }" report "whole frame", image report "face crop", image.crop box Run it as python portrait check.py photo.jpg 530 40 750 320 , where the four numbers are the left, top, right and bottom of the face. Those are the coordinates I used on the calibration photo. Keep the box tight, because hair, sky and background inside it drag the score back toward the whole-frame number. The same run gave channel means of R 142.5, G 131.5 and B 108.5 for the whole frame, a red-to-blue ratio of 1.31. The face crop came out at R 78.1, G 65.0 and B 57.8, a ratio of 1.35. My first plan was to flag any sunlit portrait whose face reads cooler than its background as a relighting error. I dropped that once I looked at how far apart real daylight sources are. Nikon's Z 7 manual lists its direct sunlight preset at about 5200 K and its shade preset at about 8000 K, so a person standing in open shade in front of sunlit trees really is lit by bluer light than the leaves behind them. A cooler face is what a camera would record there. Skin is also redder than foliage to begin with, which is probably most of why the face came out slightly warmer than the frame in my photo. So the ratio is only good for comparing frames inside one batch. If most of the batch sits near one value and two frames land far from it, open those two at full size. I have not worked out where the cut-off should be. The check I cannot script is shadow direction. Adobe's golden hour guide says a low sun throws longer shadows and that shooting into the light leaves the subject's face in shadow. A generated portrait with a bright rim of light behind the hair and an evenly lit face is implying a second light source. Photographers get that look with a reflector or fill flash, so it proves nothing by itself, but it is where I look first. In-body image goes here: outdoor-portrait-inbody.jpg, see source below I have no delivery time to report. The speed half of the test is a procedure: PFPMaker is my pick, and the reason is dull. The outdoor portrait generator https://pfpmaker.ai/outdoor-photos on its site puts claims in writing that a script and a stopwatch can check. The page says the portraits come with natural sunlight, green landscapes and open-air settings, that an order returns dozens of photos and that they are ready in under 10 minutes. Dozens of files per order is what makes a batch statistic like the red-to-blue ratio usable at all. The commercial terms are specific too. It is a one-time purchase with no subscription, and full commercial rights are included with every order. There is a money-back guarantee if you do not get at least one usable photo, provided you email within 7 days of receiving the batch. Three things on that page would go in my notes as constraints. There is no retouching of individual photos, so the fix for a bad frame is to regenerate. The page says customers find 60-80% of a batch great, which means planning to discard somewhere between a fifth and two fifths of the frames. And it gives no pixel dimensions, only "high resolution". No other generator goes on my list until this one has been through the script. For each generated file: im.size Per order, I would add the two timestamps and the number of input photos. The pixel size is the first thing I would look at, since it is the one figure the page leaves out and the script prints it on its first line.