When an AI tool stages an empty room, the furniture is the easy part to judge. The hard part is whether the room itself is still the same room. If a window shrank or a wall moved by a hand's width, a buyer will notice at the showing, even if the sofa looks great.
This post describes a simple way to measure that with OpenCV. It is the idea behind the "architecture drift" score in our benchmark, shown here as a sketch rather than the full production script.
Step 1: separate re-framing from real changes
Many tools return the same room, slightly zoomed or shifted. That is a framing problem, not moved walls, so we estimate the geometric transform first. We match SIFT features on the top part of the frame only (walls and ceiling, where furniture rarely sits) and fit a similarity transform (scale, rotation, shift) with RANSAC. Furniture and decor become outliers and are ignored.
import cv2
import numpy as np
def estimate_transform(orig_gray, out_gray, wall_fraction=0.65):
h = int(orig_gray.shape[0] * wall_fraction)
sift = cv2.SIFT_create()
k1, d1 = sift.detectAndCompute(orig_gray[:h], None)
k2, d2 = sift.detectAndCompute(out_gray[:h], None)
matches = cv2.BFMatcher().knnMatch(d2, d1, k=2)
good = [m for m, n in matches if m.distance < 0.75 * n.distance]
src = np.float32([k2[m.queryIdx].pt for m in good])
dst = np.float32([k1[m.trainIdx].pt for m in good])
matrix, inliers = cv2.estimateAffinePartial2D(src, dst, method=cv2.RANSAC)
return matrix, int(inliers.sum()) if inliers is not None else 0
The matrix tells you how much the output was zoomed, shifted and rotated. Those numbers are worth reporting on their own.
Step 2: compare structure, not pixels
After aligning the output to the original, compare only the structural parts of the image. Compute edge maps and compare them on the overlapping region, ignoring the floor where furniture stands.
from skimage.metrics import structural_similarity as ssim
def edge_map(gray):
return cv2.Canny(cv2.GaussianBlur(gray, (5, 5), 0), 60, 160)
def drift_ssim(orig_gray, aligned_gray):
a = cv2.GaussianBlur(edge_map(orig_gray), (7, 7), 0)
b = cv2.GaussianBlur(edge_map(aligned_gray), (7, 7), 0)
return ssim(a, b)
Blurring the edge maps first makes the score tolerant of a pixel or two of difference, so it reacts to a moved window and not to anti-aliasing.
Step 3: check that long straight lines survived
Walls, window frames and door frames are long straight lines. Detect them in the original with a Hough transform, then check what share of them is still present in the output within a few pixels. A low recall means structure was lost or moved.
What this does not solve
Edge comparison cannot tell a good redraw from a bad one, and it does not rate realism. It answers one narrow question: is the architecture still where it was? We keep realism ratings separate for that reason.
Try it on your own photos
Run the same tool on the same photo twice and see how much the score varies, then compare two tools. If you want to see the scores for ten fixed rooms side by side, along with the documented method and thresholds, they are on stagingbench.org, and the checks are described on the methodology page.
Top comments (0)