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

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +70 -101
main.py CHANGED
@@ -1,6 +1,6 @@
1
  """
2
- Marksman AI Pro - Multi-Model Ensemble Engine (Spyne Architecture)
3
- Models: YOLOv8-Cls (Perception) + BiRefNet (Segmentation) + OpenCV (Grounding)
4
  """
5
 
6
  import io, logging, traceback, numpy as np, cv2, torch
@@ -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 Spyne-Grade API", version="5.0.0")
16
  app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
17
 
18
  # ──────────────────────────────────────────────────────────────────
19
- # 1. ENSEMBLE MODEL LOADING
20
  # ──────────────────────────────────────────────────────────────────
21
  _models = {}
22
 
@@ -39,154 +39,123 @@ def get_model(name: str):
39
  }
40
  elif name == "yolo":
41
  from ultralytics import YOLO
42
- model = YOLO("yolov8n-cls.pt")
43
- model.to(device)
44
- _models[name] = {"model": model}
45
  return _models[name]
46
 
47
  # ──────────────────────────────────────────────────────────────────
48
- # 2. PHYSICS LOGIC: GROUNDING & SHADOWS
49
  # ──────────────────────────────────────────────────────────────────
50
 
51
- def generate_multi_layer_shadow(car_mask, bg_size, pos, v_type="low"):
52
- """Spyne's Ultra-Soft Shadow Blend"""
53
- bg_w, bg_h = bg_size
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  cw, ch = car_mask.size
55
  px, py = pos
56
  mask_np = np.array(car_mask).astype(np.float32) / 255.0
57
-
58
- # Shadow Settings for White Floors (Subtle is key)
59
- h_ratio = 0.16 if v_type == "high" else 0.10
60
- # White background par opacity 110-130 best hai
61
- opacity = 125
62
-
63
- shadow_h = int(ch * h_ratio)
64
- shadow_base = cv2.resize(mask_np, (cw, shadow_h))
65
 
66
- # Layer 1: Contact (Tires ko chipkanay ke liye - Very Tight)
67
- contact = cv2.GaussianBlur(shadow_base, (5, 3), 0)
68
- # Layer 2: Ambient (Soft spread)
69
- ambient = cv2.GaussianBlur(shadow_base, (int(cw * 0.1)|1, int(shadow_h * 0.9)|1), 0)
70
 
71
- # Blending Layers: 80% Contact, 20% Ambient
72
- combined = (contact * 0.8) + (ambient * 0.2)
 
73
 
74
- canvas = np.zeros((bg_h, bg_w), dtype=np.float32)
75
- # POSITION FIX: Shadow ko tires ke thora sa aur upar "anchor" karna (30% offset)
76
- target_y = py + ch - int(shadow_h * 0.45)
77
-
78
- y1, y2 = max(target_y, 0), min(target_y + shadow_h, bg_h)
79
- x1, x2 = px, min(px + cw, bg_w)
80
-
81
- if (y2 - y1) > 0:
82
- canvas[y1:y2, x1:x2] = combined[:y2-y1, :x2-x1]
83
-
84
- shadow_rgba = np.zeros((bg_h, bg_w, 4), dtype=np.uint8)
85
- shadow_rgba[..., 3] = (np.clip(canvas, 0, 1) * opacity).astype(np.uint8)
86
- return Image.fromarray(shadow_rgba, "RGBA")
87
-
88
- def harmonize_lighting(car_rgb, car_mask, bg_rgb):
89
- """Studio Brightness Adjustment"""
90
- # Agar background white hai, toh car ko thora 'bright' hona chahiye
91
- res = car_rgb.astype(np.float32)
92
 
93
- # Global brightness boost for white studios (5% boost)
94
- res = np.clip(res * 1.05, 0, 255)
95
 
96
- # Environment Matching
97
- avg_env = bg_rgb.mean(axis=(0,1))
98
- for c in range(3):
99
- res[..., c] = (res[..., c] * 0.94) + (avg_env[c] * 0.06)
100
-
101
- return res.astype(np.uint8)
102
 
103
- def generate_subtle_reflection(car_rgba, bg_size, pos):
104
- """Studio-grade floor reflection"""
105
  cw, ch = car_rgba.size
106
- refl = car_rgba.transpose(Image.FLIP_TOP_BOTTOM).resize((cw, int(ch * 0.22)), Image.LANCZOS)
107
- refl = refl.filter(ImageFilter.GaussianBlur(radius=6))
108
 
109
  canvas = Image.new("RGBA", bg_size, (0,0,0,0))
110
- # Paste with 7% opacity (Spyne Standard)
111
  canvas.paste(refl, (pos[0], pos[1] + ch - 4))
 
 
112
  r_np = np.array(canvas).astype(np.float32)
113
- r_np[..., 3] *= 0.07
114
  return Image.fromarray(r_np.astype(np.uint8), "RGBA")
115
 
116
  # ──────────────────────────────────────────────────────────────────
117
- # 3. ENSEMBLE PIPELINE
118
  # ──────────────────────────────────────────────────────────────────
119
 
120
- def run_ensemble_pipeline(car_pil, bg_pil):
121
- # Standardize input for AI models
122
  car_pil = car_pil.resize((1024, int(1024 * car_pil.height/car_pil.width)), Image.LANCZOS)
123
  bg_pil = bg_pil.resize((1600, int(1600 * bg_pil.height/bg_pil.width)), Image.LANCZOS)
124
 
125
- # A. Perception Phase (Classification)
126
- yolo_bundle = get_model("yolo")
127
- cls_res = yolo_bundle["model"](car_pil, verbose=False)
128
- v_class = cls_res[0].names[cls_res[0].probs.top1].lower()
129
- v_type = "high" if any(x in v_class for x in ['suv','truck','van','jeep','bus']) else "low"
130
- logger.info(f"Spyne Engine Classified: {v_type.upper()}")
131
 
132
- # B. Extraction Phase (High-Res Masking)
133
- biref_bundle = get_model("birefnet")
134
- inp = biref_bundle["transform"](car_pil.convert("RGB")).unsqueeze(0).to(biref_bundle["device"]).float()
135
  with torch.no_grad():
136
- biref_bundle["model"].float()
137
- preds = biref_bundle["model"](inp)
138
-
139
  mask = cv2.resize(torch.sigmoid(preds[-1]).squeeze().cpu().numpy(), car_pil.size)
140
 
141
- # C. Positioning Phase (Auto-Scale to 95%)
142
  target_w = int(bg_pil.size[0] * 0.95)
143
  scale = target_w / car_pil.size[0]
144
  car_res = car_pil.resize((target_w, int(car_pil.size[1]*scale)), Image.LANCZOS)
145
  mask_res = cv2.resize(mask, car_res.size)
146
 
147
- # D. Ground Perspective Detection
148
  bg_gray = np.array(bg_pil.convert("L"))
149
  edges = cv2.Canny(bg_gray[int(bg_pil.size[1]*0.7):, :], 50, 150)
150
- 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.90)
151
 
152
  px = (bg_pil.size[0] - target_w) // 2
153
- # Find the lowest tire pixel to anchor the car
154
  py = ground_y - int(np.where(mask_res > 0.5)[0].max())
155
- py = max(5, min(py, bg_pil.size[1] - car_res.size[1] - 5))
156
- pos = (px, py)
157
 
158
- # E. Color Harmonization
159
- bg_np = np.array(bg_pil)
160
- sky_tone = bg_np[:int(bg_np.shape[0]*0.2), :].mean(axis=(0,1))
161
- ground_tone = bg_np[int(bg_np.shape[0]*0.8):, :].mean(axis=(0,1))
162
- env_avg = (sky_tone + ground_tone) / 2
163
 
164
- car_np = np.array(car_res).astype(np.float32)
165
- for c in range(3):
166
- car_np[..., c] = (car_np[..., c] * 0.96) + (env_avg[c] * 0.04)
 
 
 
 
167
 
168
- car_ready = Image.fromarray(np.dstack([car_np.astype(np.uint8), (mask_res*255).astype(np.uint8)]), "RGBA")
169
-
170
- # F. Multi-Layer Compositing (Order: Reflection -> Shadow -> Car)
171
- final_canvas = bg_pil.convert("RGBA")
172
- final_canvas = Image.alpha_composite(final_canvas, generate_subtle_reflection(car_ready, bg_pil.size, pos))
173
- final_canvas = Image.alpha_composite(final_canvas, generate_multi_layer_shadow(car_ready.split()[-1], bg_pil.size, pos, v_type))
174
- final_canvas.paste(car_ready, pos, car_ready)
175
-
176
- return final_canvas.convert("RGB")
177
-
178
- # ──────────────────────────────────────────────────────────────────
179
- # 4. API ENDPOINT
180
- # ──────────────────────────────────────────────────────────────────
181
 
182
  @app.post("/composite")
183
  async def composite(car_image: UploadFile = File(...), background_image: UploadFile = File(...)):
184
  try:
185
  c_pil = Image.open(io.BytesIO(await car_image.read())).convert("RGB")
186
  b_pil = Image.open(io.BytesIO(await background_image.read())).convert("RGB")
187
- result = run_ensemble_pipeline(c_pil, b_pil)
188
  buf = io.BytesIO()
189
- result.save(buf, format="JPEG", quality=95)
190
  buf.seek(0)
191
  return StreamingResponse(buf, media_type="image/jpeg")
192
  except Exception as e:
 
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
 
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
 
 
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():
118
+ biref["model"].float()
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)
160
  return StreamingResponse(buf, media_type="image/jpeg")
161
  except Exception as e: