Khan19970 commited on
Commit
bf36d2f
Β·
verified Β·
1 Parent(s): d7b6435

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +87 -81
main.py CHANGED
@@ -1,10 +1,10 @@
1
  """
2
- Marksman AI - Professional Spyne-Grade Engine
3
- Logic: Original Shadow Extraction + Multi-Layer Compositing + Floor Reflection
4
  """
5
 
6
  import io, logging, traceback, numpy as np, cv2, torch
7
- from PIL import Image, ImageOps, ImageFilter
8
  from fastapi import FastAPI, File, UploadFile, HTTPException
9
  from fastapi.responses import StreamingResponse
10
  from fastapi.middleware.cors import CORSMiddleware
@@ -12,11 +12,11 @@ from fastapi.middleware.cors import CORSMiddleware
12
  logging.basicConfig(level=logging.INFO)
13
  logger = logging.getLogger(__name__)
14
 
15
- app = FastAPI(title="Marksman AI Pro API", version="6.0.0")
16
  app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
17
 
18
  # ──────────────────────────────────────────────────────────────────
19
- # 1. AI ENSEMBLE (Models)
20
  # ──────────────────────────────────────────────────────────────────
21
  _models = {}
22
 
@@ -24,94 +24,91 @@ def get_model(name: str):
24
  if name not in _models:
25
  device = "cuda" if torch.cuda.is_available() else "cpu"
26
  if name == "birefnet":
27
- from transformers import AutoModelForImageSegmentation
28
  from torchvision import transforms
29
  model = AutoModelForImageSegmentation.from_pretrained("ZhengPeng7/BiRefNet_dynamic", trust_remote_code=True)
30
  model.to(device).eval().float()
31
- _models[name] = {
32
- "model": model,
33
- "device": device,
34
- "transform": transforms.Compose([
35
- transforms.Resize((1024, 1024)),
36
- transforms.ToTensor(),
37
- transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
38
- ])
39
- }
40
  elif name == "yolo":
41
  from ultralytics import YOLO
42
  _models[name] = {"model": YOLO("yolov8n-cls.pt")}
43
  return _models[name]
44
 
45
  # ──────────────────────────────────────────────────────────────────
46
- # 2. THE SECRET SAUCE: COMPOSITING LOGIC
47
  # ──────────────────────────────────────────────────────────────────
48
 
49
- def apply_spyne_lighting(car_np, bg_np, mask):
50
- """Gaari ke colors ko background se match karne ki logic (Harmonization)"""
51
- bg_avg = bg_np.mean(axis=(0,1))
52
- car_avg = car_np[mask > 0.5].mean(axis=0)
53
-
54
- # 5% tint from background for realism
55
- res = car_np.astype(np.float32)
56
- for i in range(3):
57
- res[..., i] = (res[..., i] * 0.95) + (bg_avg[i] * 0.05)
58
-
59
- # Luminance matching
60
- diff = (bg_avg.mean() * 0.9 - car_avg.mean()) * 0.2
61
- return np.clip(res + diff, 0, 255).astype(np.uint8)
62
 
63
- def create_pro_shadow(car_mask, bg_size, pos, v_type="low"):
64
- """Multi-layer shadow: Silhouette + Contact + Ambient"""
 
 
65
  cw, ch = car_mask.size
66
- px, py = pos
67
- mask_np = np.array(car_mask).astype(np.float32) / 255.0
 
 
 
68
 
69
- # 1. SILHOUETTE SQUASH
70
- s_h = int(ch * (0.18 if v_type == "high" else 0.12))
71
- s_base = cv2.resize(mask_np, (cw, s_h))
72
 
73
- # 2. DUAL BLUR (Contact vs Ambient)
74
- contact = cv2.GaussianBlur(s_base, (7, 3), 0) # Sharp grounding
75
- ambient = cv2.GaussianBlur(s_base, (int(cw*0.08)|1, int(s_h*0.7)|1), 0) # Soft spread
76
 
77
- combined = (contact * 0.75) + (ambient * 0.25)
 
 
78
 
79
- canvas = Image.new("RGBA", bg_size, (0,0,0,0))
80
- shadow_img = Image.fromarray((combined * 160).astype(np.uint8), "L") # 160 = Opacity
81
 
82
- # Position: Anchor to tire line
83
- canvas.paste((0,0,0,255), (px, py + ch - int(s_h * 0.4)), mask=shadow_img)
 
 
 
 
 
 
 
 
 
84
  return canvas
85
 
86
- def create_pro_reflection(car_rgba, bg_size, pos):
87
- """Subtle floor reflection logic"""
88
- cw, ch = car_rgba.size
89
- refl = car_rgba.transpose(Image.FLIP_TOP_BOTTOM).resize((cw, int(ch * 0.25)), Image.LANCZOS)
90
- refl = refl.filter(ImageFilter.GaussianBlur(radius=8))
91
-
92
- canvas = Image.new("RGBA", bg_size, (0,0,0,0))
93
- canvas.paste(refl, (pos[0], pos[1] + ch - 4))
94
-
95
- # Fade out reflection
96
- r_np = np.array(canvas).astype(np.float32)
97
- r_np[..., 3] *= 0.06 # Spyne standard reflection opacity (6%)
98
- return Image.fromarray(r_np.astype(np.uint8), "RGBA")
99
-
100
  # ──────────────────────────────────────────────────────────────────
101
- # 3. THE ENSEMBLE PIPELINE
102
  # ──────────────────────────────────────────────────────────────────
103
 
104
- def run_spyne_pipeline(car_pil, bg_pil):
105
- # Standardize
106
  car_pil = car_pil.resize((1024, int(1024 * car_pil.height/car_pil.width)), Image.LANCZOS)
107
  bg_pil = bg_pil.resize((1600, int(1600 * bg_pil.height/bg_pil.width)), Image.LANCZOS)
108
 
109
- # A. Classification (SUV vs Sedan)
110
  yolo = get_model("yolo")["model"]
111
  res_cls = yolo(car_pil, verbose=False)
112
  v_type = "high" if any(x in res_cls[0].names[res_cls[0].probs.top1].lower() for x in ['suv','truck','van','jeep']) else "low"
113
 
114
- # B. AI Masking
115
  biref = get_model("birefnet")
116
  inp = biref["transform"](car_pil.convert("RGB")).unsqueeze(0).to(biref["device"]).float()
117
  with torch.no_grad():
@@ -119,41 +116,50 @@ def run_spyne_pipeline(car_pil, bg_pil):
119
  preds = biref["model"](inp)
120
  mask = cv2.resize(torch.sigmoid(preds[-1]).squeeze().cpu().numpy(), car_pil.size)
121
 
122
- # C. Auto-Positioning (Size 95%)
123
  target_w = int(bg_pil.size[0] * 0.95)
124
  scale = target_w / car_pil.size[0]
125
  car_res = car_pil.resize((target_w, int(car_pil.size[1]*scale)), Image.LANCZOS)
126
- mask_res = cv2.resize(mask, car_res.size)
127
 
128
- # Ground detection
129
  bg_gray = np.array(bg_pil.convert("L"))
130
  edges = cv2.Canny(bg_gray[int(bg_pil.size[1]*0.7):, :], 50, 150)
131
  ground_y = int(bg_pil.size[1]*0.7 + np.argmax(np.sum(edges, axis=1))) if edges.max() > 0 else int(bg_pil.size[1]*0.88)
132
 
133
  px = (bg_pil.size[0] - target_w) // 2
134
- py = ground_y - int(np.where(mask_res > 0.5)[0].max())
 
 
135
  pos = (px, max(5, min(py, bg_pil.size[1] - car_res.size[1] - 5)))
136
 
137
- # D. Compositing (Layering)
138
- car_harmonized = apply_spyne_lighting(np.array(car_res), np.array(bg_pil), mask_res)
139
- car_final = Image.fromarray(np.dstack([car_harmonized, (mask_res*255).astype(np.uint8)]), "RGBA")
140
-
141
- result = bg_pil.convert("RGBA")
142
- # 1. Reflection Layer
143
- result = Image.alpha_composite(result, create_pro_reflection(car_final, bg_pil.size, pos))
144
- # 2. Multi-Layer Shadow Layer
145
- result = Image.alpha_composite(result, create_pro_shadow(car_final.split()[-1], bg_pil.size, pos, v_type))
146
- # 3. Top Car Layer
147
- result.paste(car_final, pos, car_final)
148
-
149
- return result.convert("RGB")
 
 
 
 
 
 
 
150
 
151
  @app.post("/composite")
152
  async def composite(car_image: UploadFile = File(...), background_image: UploadFile = File(...)):
153
  try:
154
  c_pil = Image.open(io.BytesIO(await car_image.read())).convert("RGB")
155
  b_pil = Image.open(io.BytesIO(await background_image.read())).convert("RGB")
156
- res = run_spyne_pipeline(c_pil, b_pil)
157
  buf = io.BytesIO()
158
  res.save(buf, format="JPEG", quality=95)
159
  buf.seek(0)
 
1
  """
2
+ Marksman AI - SPYNE ENTERPRISE REPLICATION
3
+ Logic: Distance-Field Shadows, Multiply Blending, Grounded Reflection
4
  """
5
 
6
  import io, logging, traceback, numpy as np, cv2, torch
7
+ from PIL import Image, ImageFilter
8
  from fastapi import FastAPI, File, UploadFile, HTTPException
9
  from fastapi.responses import StreamingResponse
10
  from fastapi.middleware.cors import CORSMiddleware
 
12
  logging.basicConfig(level=logging.INFO)
13
  logger = logging.getLogger(__name__)
14
 
15
+ app = FastAPI(title="Spyne-Grade Engine", version="7.0.0")
16
  app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
17
 
18
  # ──────────────────────────────────────────────────────────────────
19
+ # 1. MODELS (Clean Logic)
20
  # ──────────────────────────────────────────────────────────────────
21
  _models = {}
22
 
 
24
  if name not in _models:
25
  device = "cuda" if torch.cuda.is_available() else "cpu"
26
  if name == "birefnet":
27
+ from transformers import AutoModelForImageSegmentation, AutoConfig
28
  from torchvision import transforms
29
  model = AutoModelForImageSegmentation.from_pretrained("ZhengPeng7/BiRefNet_dynamic", trust_remote_code=True)
30
  model.to(device).eval().float()
31
+ _models[name] = {"model": model, "device": device, "transform": transforms.Compose([
32
+ transforms.Resize((1024, 1024)),
33
+ transforms.ToTensor(),
34
+ transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
35
+ ])}
 
 
 
 
36
  elif name == "yolo":
37
  from ultralytics import YOLO
38
  _models[name] = {"model": YOLO("yolov8n-cls.pt")}
39
  return _models[name]
40
 
41
  # ──────────────────────────────────────────────────────────────────
42
+ # 2. THE SPYNE "SECRET": MULTIPLY BLENDING & SHADOW FIELD
43
  # ──────────────────────────────────────────────────────────────────
44
 
45
+ def multiply_blend(background, shadow_mask, opacity=0.7):
46
+ """
47
+ Asli Spyne logic: Shadow ko 'Paste' nahi karte, background ko 'Darken' karte hain.
48
+ Is se background ki tiles/road shadow ke niche nazar aati hain.
49
+ """
50
+ bg_float = background.astype(np.float32) / 255.0
51
+ # Shadow mask ko intensity mein convert karen
52
+ mask = shadow_mask.astype(np.float32) / 255.0
53
+ mask = mask[..., None] # Add channel dim
54
+
55
+ # Multiply: New_Pixel = Old_Pixel * (1 - Mask * Opacity)
56
+ darkened = bg_float * (1.0 - (mask * opacity))
57
+ return (darkened * 255).astype(np.uint8)
58
 
59
+ def generate_spyne_shadow(car_mask, bg_size, pos, v_type="low"):
60
+ """
61
+ Distance Transform based shadow: Jo car ke kareeb dark aur door jate blur ho jaye.
62
+ """
63
  cw, ch = car_mask.size
64
+ mask_np = (np.array(car_mask) > 128).astype(np.uint8) * 255
65
+
66
+ # 1. Create a flattened silhouette
67
+ s_h = int(ch * (0.22 if v_type == "high" else 0.14))
68
+ silhouette = cv2.resize(mask_np, (cw, s_h))
69
 
70
+ # 2. Distance Field (Edges ko soft karne ke liye)
71
+ dist_map = cv2.distanceTransform(silhouette, cv2.DIST_L2, 5)
72
+ dist_map = cv2.normalize(dist_map, None, 0, 1.0, cv2.NORM_MINMAX)
73
 
74
+ # 3. Layer 1: Contact (Super sharp thin line)
75
+ contact = cv2.GaussianBlur(silhouette, (5, 3), 0)
 
76
 
77
+ # Layer 2: Ambient (The soft spread)
78
+ ambient_blur = int(cw * 0.1) | 1
79
+ ambient = cv2.GaussianBlur(silhouette, (ambient_blur, int(s_h * 0.8) | 1), 0)
80
 
81
+ # Mix layers
82
+ final_mask = (contact.astype(float) * 0.6) + (ambient.astype(float) * 0.4)
83
 
84
+ # Full Canvas placement
85
+ canvas = np.zeros((bg_size[1], bg_size[0]), dtype=np.uint8)
86
+ # Anchor: Spyne hamesha shadow ko car ke 40% height offset par rakhta hai
87
+ target_y = pos[1] + ch - int(s_h * 0.42)
88
+
89
+ y1, y2 = max(target_y, 0), min(target_y + s_h, bg_size[1])
90
+ x1, x2 = pos[0], min(pos[0] + cw, bg_size[0])
91
+
92
+ if (y2 - y1) > 0:
93
+ canvas[y1:y2, x1:x2] = final_mask[:y2-y1, :x2-x1].astype(np.uint8)
94
+
95
  return canvas
96
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  # ──────────────────────────────────────────────────────────────────
98
+ # 3. THE PIPELINE
99
  # ──────────────────────────────────────────────────────────────────
100
 
101
+ def run_pro_pipeline(car_pil, bg_pil):
102
+ # Resize
103
  car_pil = car_pil.resize((1024, int(1024 * car_pil.height/car_pil.width)), Image.LANCZOS)
104
  bg_pil = bg_pil.resize((1600, int(1600 * bg_pil.height/bg_pil.width)), Image.LANCZOS)
105
 
106
+ # A. Classification
107
  yolo = get_model("yolo")["model"]
108
  res_cls = yolo(car_pil, verbose=False)
109
  v_type = "high" if any(x in res_cls[0].names[res_cls[0].probs.top1].lower() for x in ['suv','truck','van','jeep']) else "low"
110
 
111
+ # B. Segmentation
112
  biref = get_model("birefnet")
113
  inp = biref["transform"](car_pil.convert("RGB")).unsqueeze(0).to(biref["device"]).float()
114
  with torch.no_grad():
 
116
  preds = biref["model"](inp)
117
  mask = cv2.resize(torch.sigmoid(preds[-1]).squeeze().cpu().numpy(), car_pil.size)
118
 
119
+ # C. Sizing (95% Spyne Standard)
120
  target_w = int(bg_pil.size[0] * 0.95)
121
  scale = target_w / car_pil.size[0]
122
  car_res = car_pil.resize((target_w, int(car_pil.size[1]*scale)), Image.LANCZOS)
123
+ mask_res = (cv2.resize(mask, car_res.size) * 255).astype(np.uint8)
124
 
125
+ # D. Ground Detection
126
  bg_gray = np.array(bg_pil.convert("L"))
127
  edges = cv2.Canny(bg_gray[int(bg_pil.size[1]*0.7):, :], 50, 150)
128
  ground_y = int(bg_pil.size[1]*0.7 + np.argmax(np.sum(edges, axis=1))) if edges.max() > 0 else int(bg_pil.size[1]*0.88)
129
 
130
  px = (bg_pil.size[0] - target_w) // 2
131
+ # Find exact tire contact point
132
+ bottom_y = np.where(mask_res > 128)[0].max()
133
+ py = ground_y - bottom_y
134
  pos = (px, max(5, min(py, bg_pil.size[1] - car_res.size[1] - 5)))
135
 
136
+ # E. The Compositing (NumPy Multiply)
137
+ bg_np = np.array(bg_pil.convert("RGB"))
138
+ car_np = np.array(car_res.convert("RGB"))
139
+
140
+ # 1. Shadow Step (Multiply Blend)
141
+ shadow_mask = generate_spyne_shadow(Image.fromarray(mask_res), bg_pil.size, pos, v_type)
142
+ # Background ko shadow se darken karo (Opacity 0.75 for realism)
143
+ blended_bg = multiply_blend(bg_np, shadow_mask, opacity=0.75)
144
+
145
+ # 2. Reflection Step (Subtle)
146
+ final_img = Image.fromarray(blended_bg).convert("RGBA")
147
+ refl = car_res.transpose(Image.FLIP_TOP_BOTTOM).resize((target_w, int(car_res.size[1]*0.25)))
148
+ refl_mask = Image.fromarray((np.array(refl.convert("L")) * 0.08).astype(np.uint8)) # 8% opacity
149
+ final_img.paste(refl, (pos[0], pos[1] + car_res.size[1] - 2), mask=refl_mask)
150
+
151
+ # 3. Car Step
152
+ car_rgba = Image.fromarray(np.dstack([car_np, mask_res]))
153
+ final_img.paste(car_rgba, pos, car_rgba)
154
+
155
+ return final_img.convert("RGB")
156
 
157
  @app.post("/composite")
158
  async def composite(car_image: UploadFile = File(...), background_image: UploadFile = File(...)):
159
  try:
160
  c_pil = Image.open(io.BytesIO(await car_image.read())).convert("RGB")
161
  b_pil = Image.open(io.BytesIO(await background_image.read())).convert("RGB")
162
+ res = run_pro_pipeline(c_pil, b_pil)
163
  buf = io.BytesIO()
164
  res.save(buf, format="JPEG", quality=95)
165
  buf.seek(0)