Privacy as a creative constraint
Photographs I could not publish, anonymized and then handed to an AI with one instruction: remove the body, keep the clothes. What remains is a picture of absence.
Privacy usually reaches a photograph as subtraction. A face gets blurred, an image gets discarded, an archive gets locked away, and what survives is compliant in the way a redacted document is compliant: legally fine, visually dead, and no longer really saying anything. I had a set of photographs in exactly that trap. Real people in real spaces, images with documentary and aesthetic value, and no way to publish or process them further without risking personal identification. The faces were already heavily obscured, the identities already erased in a legal sense, and still the presence of bodies raised questions.
The standard move at that point is to push the images further into obscurity until the questions stop. I went the other way, and leaned into transformation instead.
The idea is simple to state. Take photographs that have already been fully anonymized, and use a generative model to remove the human body entirely while preserving clothing, posture and spatial context. What remains looks like garments suspended in space, empty shells still holding the shape of the person who filled them. It is neither surveillance nor concealment. Privacy stops being a constraint to work around and becomes the conceptual engine that generates the visual language itself.
Anonymize first
Nothing touched a generative model until it had been through a strict anonymization pass, and I treated that ordering as a foundation rather than a formality. Every photograph was processed to make faces completely unrecognizable, removing any realistic possibility of identification, so that the material was fully compliant with privacy principles before any artistic transformation began.
The pass itself is programmatic: a small Python tool I wrote around OpenCV’s YuNet face detector, which finds every face and buries it under a deliberately excessive amount of Gaussian blur, repeated until nothing recoverable is left. Automation gave me consistency and scale across the whole set, but I did not treat it as sufficient on its own. I then reviewed every image manually, one by one, looking for missed detections and edge cases, and only the photographs that survived that second, human check were admitted to the next phase.


The tool is short enough to publish whole, and publishing it is part of the argument: the compliance step should be as inspectable as the creative one.
import os
import glob
import cv2
# ====== CONFIG ======
INPUT_DIR = "./FOLDER"
OUTPUT_DIR = "./FOLDER_blurred"
MODEL_PATH = "./face_detection_yunet_2023mar.onnx"
IMAGE_EXTS = (".jpg", ".jpeg", ".png", ".bmp", ".tif", ".tiff", ".webp")
# Detection (if too many / too few faces are detected, tweak score_threshold)
SCORE_THRESHOLD = 0.80
NMS_THRESHOLD = 0.30
TOP_K = 500
# ====== HEAVY BLUR (very aggressive) ======
BLUR_SCALE = 2.5 # increase for stronger blur (e.g. 3.0)
MAX_K = 401 # kernel size cap (very large)
REPEATS = 4 # blur repetitions (increase for more "censorship")
def odd(n: int) -> int:
return n if n % 2 == 1 else n + 1
def list_images(folder: str):
files = []
for ext in IMAGE_EXTS:
files += glob.glob(os.path.join(folder, f"*{ext}"))
files += glob.glob(os.path.join(folder, f"*{ext.upper()}"))
return sorted(files)
def heavy_blur_roi(roi_bgr):
"""
Very aggressive and stable blur:
- kernel proportional to face size (min ROI dimension)
- repeated blur passes
"""
if roi_bgr is None or roi_bgr.size == 0:
return roi_bgr
base = max(25, int(min(roi_bgr.shape[:2]) * BLUR_SCALE))
k = min(MAX_K, odd(base * 2 + 1)) # very large, odd kernel size
blurred = roi_bgr
for _ in range(REPEATS):
blurred = cv2.GaussianBlur(blurred, (k, k), 0)
return blurred
def main():
if not os.path.exists(MODEL_PATH):
raise FileNotFoundError(
f"YuNet model not found: {MODEL_PATH}\n"
"Download it from:\n"
"https://github.com/opencv/opencv_zoo/raw/main/models/face_detection_yunet/face_detection_yunet_2023mar.onnx"
)
os.makedirs(OUTPUT_DIR, exist_ok=True)
detector = cv2.FaceDetectorYN.create(
MODEL_PATH,
"",
(640, 640),
score_threshold=SCORE_THRESHOLD,
nms_threshold=NMS_THRESHOLD,
top_k=TOP_K,
)
files = list_images(INPUT_DIR)
if not files:
print(f"No images found in {INPUT_DIR}")
return
total_faces = 0
for path in files:
img = cv2.imread(path)
if img is None:
print(f"[SKIP] Unable to read: {path}")
continue
h, w = img.shape[:2]
detector.setInputSize((w, h))
_, faces = detector.detect(img)
out = img.copy()
n_faces = 0
if faces is not None:
for f in faces:
x, y, bw, bh = map(int, f[:4])
# Clamp bounding box
x1 = max(0, x)
y1 = max(0, y)
x2 = min(w, x + bw)
y2 = min(h, y + bh)
if x2 <= x1 or y2 <= y1:
continue
roi = out[y1:y2, x1:x2]
out[y1:y2, x1:x2] = heavy_blur_roi(roi)
n_faces += 1
out_path = os.path.join(OUTPUT_DIR, os.path.basename(path))
ok = cv2.imwrite(out_path, out)
if not ok:
print(f"[ERR ] Unable to save: {out_path}")
continue
total_faces += n_faces
print(f"[OK ] {os.path.basename(path)} -> faces blurred: {n_faces}")
print(f"Done. Output: {OUTPUT_DIR}. Total faces blurred: {total_faces}")
if __name__ == "__main__":
main()
The sequencing is the whole logic of the project. By the time any AI was involved, the photographs no longer represented people in a legal or biometric sense: they were already abstracted, already stripped of identity, already safe to process. I did not use a model to anonymize reality after the fact. I used it to reinterpret material that had already been ethically and legally neutralized. The creative process starts from a position of compliance, not remediation, and builds meaning on top of the constraint instead of trying to route around it.
The prompt
With the images neutralized, the task changed from correction to transformation, and I made a deliberately awkward choice about the tool: a general-purpose generative system, not a bespoke vision model. Part of the experiment was to see whether a mainstream AI could be steered into a coherent, controlled visual outcome by language alone.
Because language alone is what it got. No masks, no reference images, no compositing afterwards. The entire interaction is one prompt, written as a technical instruction rather than a creative suggestion, with as little interpretative freedom as I could leave in it. Its core is a strict separation of concerns: the human body is a removable volume, and the clothing is an inviolable object that must stay physically and visually consistent while the volume inside it disappears. Most of the lines exist because a model, asked to remove a body, will cheerfully remove the trousers too, or fill a collar with skin, or swap the person for a ghost. Every failure mode got its own sentence.
Here is the prompt, verbatim, because the process should be reproducible:
Remove all humans completely from the image.
The human body must be removed as a continuous invisible volume inside the
clothing, including head, neck, torso, chest, shoulders, arms, hips, legs,
and feet.
Under no circumstances remove, cut, erase, or replace any clothing items.
All garments — including pants, trousers, jeans, skirts, dresses, tights,
stockings, and tight-fitting clothes — must be fully preserved.
Clothing must always remain visible in its entirety.
Pants and lower-body garments must never be removed, shortened, faded, or
confused with human anatomy.
The absence of the body must be represented only as empty internal space
inside the clothing.
Garments must appear hollow and empty inside, with physically correct
interior volume, thickness, seams, linings, and inner folds.
Garments with deep necklines, open cuts, or tight fits must show empty
interior space, not skin, body parts, or missing sections.
Clothes must remain naturally shaped and positioned, with correct gravity,
volume, lighting, fabric tension, and realistic shadows.
Do not leave any traces of human anatomy, skin, hair, or biological elements.
Do not replace people with mannequins, ghosts, silhouettes, transparent
bodies, or abstract substitutes.
Do not alter the background, environment, or non-human objects.
This is where the project shifts from compliance to interpretation. The model is not asked to hide identities or blur reality further. It is asked to construct a visual state that could not exist without the privacy constraint that preceded it: an image that still documents a scene, but no longer depicts a person as a biological subject, only as the negative space they once occupied. The suit at the top of this post still stands the way its owner stood. It just no longer contains him.
What remains
What comes out of this process is not a workaround, and I have stopped thinking of it as one. The usual treatment of privacy in visual media reduces fidelity, adds noise, or removes content until the image is legally acceptable and visually inert. Here the opposite happens: compliance is the starting condition of the work, not its ceiling. Anonymizing fully first and transforming later also avoids the ambiguity of retroactive justification, because at no point does the creative step touch anything that could identify a person.
The resulting images are neither portraits nor erasures. They keep the context, the posture, the social space, the material culture of the moment. Clothing becomes a trace of presence rather than an attribute of identity. The bodies are not censored; they are conceptually subtracted, and the documentation of that absence turns out to be more expressive than a blurred face ever was.
I did not use artificial intelligence to extract more information from these photographs. I used it to enforce a boundary, and to enforce it with a precision that blur never offered. The work sits between documentation and fiction, between compliance and interpretation, and it suggests something I now believe more generally: privacy-aware media does not have to be visually impoverished. Treated as a design principle rather than a legal afterthought, privacy can generate entirely new ways of seeing.
A version of this essay first appeared on Medium in January 2026.
