from pathlib import Path
import cv2, numpy as np
from PIL import Image, ImageDraw, ImageFilter

outdir = Path('/Users/bot1/Volumes/root_for_ai/AI工作区/国博_产品_国宝艺展毛绒盲盒_20260620_2052/work/状元及第_用户参考保真重做_20260620')
outdir.mkdir(parents=True, exist_ok=True)
body_src = Path('/Users/bot1/.hermes/profiles/designer3/image_cache/img_ac742521be46.jpg')
ring_src = Path('/Users/bot1/Volumes/root_for_ai/AI工作区/国博_产品_国宝艺展毛绒盲盒_20260620_2052/work/状元及第_用户参考保真重做_20260620/状元及第_用户参考实物抠图_grabcut_含浅黄圆环.png')
# --- body from user front photo ---
img = cv2.cvtColor(cv2.imread(str(body_src)), cv2.COLOR_BGR2RGB)
# crop contains complete plush body and top yellow tag, excludes most background
x1, y1, x2, y2 = 130, 20, 620, 500
crop = img[y1:y2, x1:x2].copy()
h, w = crop.shape[:2]
r, g, b = crop[:,:,0], crop[:,:,1], crop[:,:,2]
hsv = cv2.cvtColor(crop, cv2.COLOR_RGB2HSV)
H, S, V = hsv[:,:,0], hsv[:,:,1], hsv[:,:,2]
mask = np.full((h,w), cv2.GC_PR_BGD, np.uint8)
# definite background: green cloth and white/gray backing card areas
white = np.logical_and.reduce((r > 175, g > 170, b > 160, S < 70))
neutral_card = np.logical_and.reduce((S < 45, V > 70, r > 80, g > 80, b > 80))
green = np.logical_and.reduce((H > 55, H < 105, S > 35, g.astype(int) > r.astype(int) + 5))
bg_seed = np.logical_or.reduce((white, neutral_card, green))
mask[bg_seed] = cv2.GC_BGD
# foreground seed: yellow plush/orange embroidery/yellow tag
fg_yellow = np.logical_and.reduce((r > 115, g > 80, b < 145, ((r.astype(int)+g.astype(int))//2 - b.astype(int)) > 25))
fg_orange = np.logical_and.reduce((r > 120, g > 45, b < 100, r.astype(int) > b.astype(int) + 45))
mask[np.logical_or(fg_yellow, fg_orange)] = cv2.GC_PR_FGD
strong = np.logical_and.reduce((r > 140, g > 100, b < 125))
mask[strong] = cv2.GC_FGD
# central body ellipse as probable/strong foreground to avoid internal holes but not background corners
ellipse = np.zeros((h,w), np.uint8)
cv2.ellipse(ellipse, (250,290), (205,190), 0, 0, 360, 255, -1)
mask[ellipse > 0] = np.where(mask[ellipse > 0] == cv2.GC_BGD, cv2.GC_PR_FGD, mask[ellipse > 0])
# tag rectangle upper center probable fg
mask[40:125,235:310] = cv2.GC_PR_FGD
bgdModel = np.zeros((1,65), np.float64)
fgdModel = np.zeros((1,65), np.float64)
cv2.grabCut(crop, mask, None, bgdModel, fgdModel, 5, cv2.GC_INIT_WITH_MASK)
alpha = np.where(np.logical_or(mask == cv2.GC_FGD, mask == cv2.GC_PR_FGD), 255, 0).astype('uint8')
# force remove white/gray/green bg after grabcut
alpha[bg_seed] = 0
# fill interior holes in main body ellipse only
body_region = ellipse > 0
alpha[body_region] = 255
# but remove any visible white/gray/green backing or green cloth in body region
alpha[np.logical_and(body_region, bg_seed)] = 0
kernel = np.ones((3,3), np.uint8)
alpha = cv2.morphologyEx(alpha, cv2.MORPH_OPEN, kernel, iterations=1)
alpha = cv2.morphologyEx(alpha, cv2.MORPH_CLOSE, kernel, iterations=2)
# keep largest comp + tag if connected/close
num, labels, stats, _ = cv2.connectedComponentsWithStats((alpha>0).astype('uint8'),8)
keep = np.zeros_like(alpha)
areas=[]
for i in range(1,num):
    area_i = stats[i, cv2.CC_STAT_AREA]
    if area_i > 50:
        areas.append((int(area_i), int(i), stats[i].tolist()))
areas = sorted(areas, reverse=True)[:3]
for _,i,_ in areas:
    keep[labels==i] = 255
alpha = keep
alpha_pil = Image.fromarray(alpha).filter(ImageFilter.GaussianBlur(0.6))
# trim
bb = alpha_pil.getbbox()
body_rgba = Image.fromarray(crop).crop(bb).convert('RGBA')
body_rgba.putalpha(alpha_pil.crop(bb))
body_path = outdir/'状元及第_用户正面实物抠图_不改产品.png'
body_rgba.save(body_path)
# --- ring from high-res closeup cutout: crop ring only, scale/rotate minimally to sit behind tag ---
ring_full = Image.open(ring_src).convert('RGBA')
# ring is in upper-left of the high-res cutout; crop around it including bottom contact point
# Crop only the real plastic ring; avoid including the extra plush/body pixels from the closeup source.
ring_crop = ring_full.crop((0, 0, 820, 660))
# trim alpha, then keep only the pale yellow-green plastic ring pixels (remove accidental tag/body fragments)
rbb = ring_crop.getchannel('A').getbbox()
ring_crop = ring_crop.crop(rbb).convert('RGBA')
arr = np.array(ring_crop)
rr, gg, bb2, aa = arr[:,:,0], arr[:,:,1], arr[:,:,2], arr[:,:,3]
ring_cond = np.logical_and.reduce((aa > 0, gg.astype(int) >= rr.astype(int) - 5, bb2 > 65, gg.astype(int) > bb2.astype(int) + 8, rr < 230))
arr[:,:,3] = np.where(ring_cond, aa, 0).astype('uint8')
ring_crop = Image.fromarray(arr, 'RGBA')
rbb = ring_crop.getchannel('A').getbbox()
ring_crop = ring_crop.crop(rbb)
# Keep only an annulus-shaped area of the plastic ring to drop any tag/body fragments that survived color filtering.
rw, rh = ring_crop.size
ann = Image.new('L', (rw, rh), 0)
ad = ImageDraw.Draw(ann)
ad.ellipse((2, 2, rw-3, rh-3), fill=255)
thick = max(10, int(min(rw, rh) * 0.30))
ad.ellipse((thick, thick, rw-thick, rh-thick), fill=0)
ra = ring_crop.getchannel('A')
ra = Image.fromarray((np.array(ra).astype(np.uint16) * np.array(ann).astype(np.uint16) // 255).astype('uint8'))
ring_crop.putalpha(ra)
rbb = ring_crop.getchannel('A').getbbox()
ring_crop = ring_crop.crop(rbb)
# resize ring relative to body: ring outer width about 42% of body width
body_w, body_h = body_rgba.size
ring_w = int(body_w * 0.48)
ring_h = int(ring_crop.height * ring_w / ring_crop.width)
ring_small = ring_crop.resize((ring_w, ring_h), Image.Resampling.LANCZOS)
# compose master canvas
canvas_w = max(body_w, ring_w) + 60
canvas_h = body_h + int(ring_h*0.72)
master = Image.new('RGBA', (canvas_w, canvas_h), (0,0,0,0))
# place ring behind top tag: ring center above tag; bottom of ring overlaps tag top area
ring_x = int((canvas_w-ring_w)/2) - int(body_w*0.02)
ring_y = 0
master.alpha_composite(ring_small, (ring_x, ring_y))
body_x = int((canvas_w-body_w)/2)
body_y = int(ring_h*0.58)
master.alpha_composite(body_rgba, (body_x, body_y))
# trim canvas
mbb = master.getchannel('A').getbbox()
master = master.crop(mbb)
# Manually clear a tiny accidental print/tag fragment from the ring closeup; the true product tag is already from the front photo.
ma = master.getchannel('A')
md = ImageDraw.Draw(ma)
md.rectangle((205, 70, 352, 205), fill=0)
master.putalpha(ma)
mbb = master.getchannel('A').getbbox()
master = master.crop(mbb)
master_path = outdir/'状元及第_用户参考正面合成master_含真实浅黄圆环.png'
master.save(master_path)
# preview
white_bg = Image.new('RGB', master.size, 'white')
white_bg.paste(master, mask=master.getchannel('A'))
preview_path = outdir/'状元及第_用户参考正面合成master_白底预览.jpg'
white_bg.save(preview_path, quality=95)
# side-by-side preview with source
src_small = Image.open(body_src).resize((750,500))
prev = Image.new('RGB',(src_small.width + white_bg.width + 30, max(src_small.height, white_bg.height)), 'white')
prev.paste(src_small,(0,0)); prev.paste(white_bg,(src_small.width+30,0))
ImageDraw.Draw(prev).text((10,10),'left user reference / right no-redraw master',fill=(0,0,0))
compare_path = outdir/'状元及第_用户参考正面master_对照预览.jpg'
prev.save(compare_path, quality=95)
print('body', body_path, body_rgba.size, 'areas', areas)
print('master', master_path, master.size)
print('preview', compare_path)
