painting-vision-robotics-kit / segment_wall.py
constructelligence's picture
Upload segment_wall.py with huggingface_hub
48f0caf verified
Raw History Blame Contribute Delete
3.7 kB
#!/usr/bin/env python3
"""HSV baseline for painted-area estimation on a user-marked wall region.
This is a visualization/data bootstrap, not a lighting-robust learned model.
"""
import argparse
import json
from pathlib import Path
import cv2
import numpy as np
def points_arg(value):
try:
pts = [[int(v) for v in pair.split(",")] for pair in value.split()]
if len(pts) < 3 or any(len(p) != 2 for p in pts):
raise ValueError
return np.asarray(pts, np.int32)
except ValueError as exc:
raise argparse.ArgumentTypeError("expected at least 3 x,y points separated by spaces") from exc
def main():
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("image", type=Path)
ap.add_argument("--paint-hsv", type=int, nargs=6, required=True,
metavar=("H_MIN", "S_MIN", "V_MIN", "H_MAX", "S_MAX", "V_MAX"))
ap.add_argument("--wall", type=points_arg, required=True, help='wall polygon: "x,y x,y x,y ..."')
ap.add_argument("--out", type=Path, default=Path("painting_overlay.png"))
ap.add_argument("--rectify", action="store_true", help="rectify a four-corner wall to a front view")
args = ap.parse_args()
image = cv2.imread(str(args.image))
if image is None:
ap.error(f"could not read image: {args.image}")
h, w = image.shape[:2]
if np.any(args.wall[:, 0] < 0) or np.any(args.wall[:, 0] >= w) or np.any(args.wall[:, 1] < 0) or np.any(args.wall[:, 1] >= h):
ap.error("wall polygon points must fall inside the image")
if args.rectify:
if len(args.wall) != 4:
ap.error("--rectify requires exactly four wall corners, ordered clockwise from top-left")
# Rectified dimensions preserve approximate source edge lengths.
p = args.wall.astype(np.float32)
width = max(int(np.linalg.norm(p[1] - p[0])), int(np.linalg.norm(p[2] - p[3])))
height = max(int(np.linalg.norm(p[3] - p[0])), int(np.linalg.norm(p[2] - p[1])))
dst = np.float32([[0, 0], [width - 1, 0], [width - 1, height - 1], [0, height - 1]])
image = cv2.warpPerspective(image, cv2.getPerspectiveTransform(p, dst), (width, height))
wall_mask = np.full((height, width), 255, np.uint8)
else:
wall_mask = np.zeros((h, w), np.uint8)
cv2.fillPoly(wall_mask, [args.wall], 255)
lo = np.array(args.paint_hsv[:3], dtype=np.uint8)
hi = np.array(args.paint_hsv[3:], dtype=np.uint8)
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
paint = cv2.inRange(hsv, lo, hi)
paint = cv2.bitwise_and(paint, wall_mask)
wall_pixels = int(cv2.countNonZero(wall_mask))
painted_pixels = int(cv2.countNonZero(paint))
coverage = painted_pixels / wall_pixels if wall_pixels else 0.0
overlay = image.copy()
overlay[paint > 0] = (0, 220, 0)
overlay[wall_mask == 0] = (overlay[wall_mask == 0] * 0.35).astype(np.uint8)
result = cv2.addWeighted(image, 0.58, overlay, 0.42, 0)
cv2.polylines(result, [args.wall] if not args.rectify else [np.array([[0, 0], [w-1, 0], [w-1, h-1], [0, h-1]])], True, (0, 255, 255), 2)
args.out.parent.mkdir(parents=True, exist_ok=True)
if not cv2.imwrite(str(args.out), result):
ap.error(f"could not write output: {args.out}")
print(json.dumps({"painted_fraction": coverage, "painted_percent": round(coverage * 100, 2),
"painted_pixels": painted_pixels, "wall_pixels": wall_pixels,
"uncertainty_fraction": None,
"note": "HSV baseline; unclassified pixels are not yet distinguished from unpainted pixels."}, indent=2))
print(f"overlay: {args.out}")
if __name__ == "__main__":
main()