Spaces:
Sleeping
Sleeping
Upload 7 files
Browse files- .gitattributes +4 -0
- app.py +163 -0
- example_images/BMW.jpg +3 -0
- example_images/Dodge.jpg +3 -0
- example_images/Ferrari.jpg +3 -0
- example_images/Porsche.jpg +3 -0
- readme_.md +71 -0
- requirements.txt +5 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,7 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
example_images/BMW.jpg filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
example_images/Dodge.jpg filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
example_images/Ferrari.jpg filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
example_images/Porsche.jpg filter=lfs diff=lfs merge=lfs -text
|
app.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
from transformers import pipeline
|
| 3 |
+
import openai
|
| 4 |
+
import base64
|
| 5 |
+
import os
|
| 6 |
+
import json
|
| 7 |
+
|
| 8 |
+
# ---------------------------------------------------------------------------
|
| 9 |
+
# Car brands β must match the classes the ViT model was trained on
|
| 10 |
+
# ---------------------------------------------------------------------------
|
| 11 |
+
CAR_BRANDS = ['BMW', 'Ferrari', 'Ford', 'Jeep', 'Lamborghini', 'Porsche', 'Rolls-Royce', 'Toyota']
|
| 12 |
+
|
| 13 |
+
# ---------------------------------------------------------------------------
|
| 14 |
+
# Load models (loaded once at startup)
|
| 15 |
+
# ---------------------------------------------------------------------------
|
| 16 |
+
|
| 17 |
+
# Custom fine-tuned ViT model (trained on Stanford Cars, 8 brand classes)
|
| 18 |
+
# Replace with your actual Hugging Face model ID after training and pushing
|
| 19 |
+
vit_classifier = pipeline(
|
| 20 |
+
"image-classification",
|
| 21 |
+
model="nenzilea/car-classification"
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
# CLIP zero-shot classifier
|
| 25 |
+
clip_classifier = pipeline(
|
| 26 |
+
model="openai/clip-vit-large-patch14",
|
| 27 |
+
task="zero-shot-image-classification"
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
# ---------------------------------------------------------------------------
|
| 31 |
+
# OpenAI helper
|
| 32 |
+
# ---------------------------------------------------------------------------
|
| 33 |
+
|
| 34 |
+
def encode_image_to_base64(image_path: str) -> str:
|
| 35 |
+
with open(image_path, "rb") as f:
|
| 36 |
+
return base64.b64encode(f.read()).decode("utf-8")
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def classify_with_openai(image_path: str) -> dict:
|
| 40 |
+
"""Send image to GPT-4o and ask it to return confidence scores per brand."""
|
| 41 |
+
api_key = os.environ.get("OPENAI_API_KEY")
|
| 42 |
+
client = openai.OpenAI(api_key=api_key)
|
| 43 |
+
|
| 44 |
+
ext = os.path.splitext(image_path)[1].lower().lstrip(".")
|
| 45 |
+
mime_type = "image/jpeg" if ext in ("jpg", "jpeg") else f"image/{ext}"
|
| 46 |
+
base64_image = encode_image_to_base64(image_path)
|
| 47 |
+
|
| 48 |
+
prompt = (
|
| 49 |
+
f"You are a car classification expert. Classify the car brand shown in this image. "
|
| 50 |
+
f"The possible classes are: {', '.join(CAR_BRANDS)}. "
|
| 51 |
+
"Respond ONLY with a valid JSON object where each key is a brand name from the list and "
|
| 52 |
+
"each value is a confidence score between 0.0 and 1.0. All scores must sum to 1.0. "
|
| 53 |
+
'Example format: {"BMW": 0.05, "Ferrari": 0.85, "Ford": 0.02, ...}'
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
response = client.chat.completions.create(
|
| 57 |
+
model="gpt-4o",
|
| 58 |
+
messages=[
|
| 59 |
+
{
|
| 60 |
+
"role": "user",
|
| 61 |
+
"content": [
|
| 62 |
+
{"type": "text", "text": prompt},
|
| 63 |
+
{
|
| 64 |
+
"type": "image_url",
|
| 65 |
+
"image_url": {"url": f"data:{mime_type};base64,{base64_image}"},
|
| 66 |
+
},
|
| 67 |
+
],
|
| 68 |
+
}
|
| 69 |
+
],
|
| 70 |
+
max_tokens=300,
|
| 71 |
+
)
|
| 72 |
+
|
| 73 |
+
text = response.choices[0].message.content
|
| 74 |
+
try:
|
| 75 |
+
start = text.find("{")
|
| 76 |
+
end = text.rfind("}") + 1
|
| 77 |
+
scores = json.loads(text[start:end])
|
| 78 |
+
return {brand: float(scores.get(brand, 0.0)) for brand in CAR_BRANDS}
|
| 79 |
+
except Exception:
|
| 80 |
+
uniform = 1.0 / len(CAR_BRANDS)
|
| 81 |
+
return {brand: uniform for brand in CAR_BRANDS}
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
# ---------------------------------------------------------------------------
|
| 85 |
+
# Main classification function
|
| 86 |
+
# ---------------------------------------------------------------------------
|
| 87 |
+
|
| 88 |
+
def classify_car(image):
|
| 89 |
+
if image is None:
|
| 90 |
+
return {}, {}, {}
|
| 91 |
+
|
| 92 |
+
# Custom ViT β fine-tuned on Stanford Cars
|
| 93 |
+
vit_results = vit_classifier(image, top_k=len(CAR_BRANDS))
|
| 94 |
+
vit_output = {r["label"]: round(r["score"], 4) for r in vit_results}
|
| 95 |
+
|
| 96 |
+
# CLIP β zero-shot with brand names as candidate labels
|
| 97 |
+
clip_results = clip_classifier(image, candidate_labels=CAR_BRANDS)
|
| 98 |
+
clip_output = {r["label"]: round(r["score"], 4) for r in clip_results}
|
| 99 |
+
|
| 100 |
+
# OpenAI GPT-4o Vision
|
| 101 |
+
openai_output = classify_with_openai(image)
|
| 102 |
+
|
| 103 |
+
return vit_output, clip_output, openai_output
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
# ---------------------------------------------------------------------------
|
| 107 |
+
# Example images (add representative car images to example_images/)
|
| 108 |
+
# ---------------------------------------------------------------------------
|
| 109 |
+
|
| 110 |
+
example_images = [
|
| 111 |
+
["example_images/ferrari.jpg"],
|
| 112 |
+
["example_images/lamborghini.jpg"],
|
| 113 |
+
["example_images/bmw.jpg"],
|
| 114 |
+
["example_images/jeep.jpg"],
|
| 115 |
+
["example_images/ford.jpg"],
|
| 116 |
+
["example_images/toyota.jpg"],
|
| 117 |
+
["example_images/porsche.jpg"],
|
| 118 |
+
["example_images/rolls_royce.jpg"],
|
| 119 |
+
]
|
| 120 |
+
|
| 121 |
+
# ---------------------------------------------------------------------------
|
| 122 |
+
# Gradio UI
|
| 123 |
+
# ---------------------------------------------------------------------------
|
| 124 |
+
|
| 125 |
+
with gr.Blocks(title="Car Brand Classification Comparison") as demo:
|
| 126 |
+
gr.Markdown("# Car Brand Classification β Model Comparison")
|
| 127 |
+
gr.Markdown(
|
| 128 |
+
"Upload a car image to compare predictions from three different models:\n\n"
|
| 129 |
+
"- **Custom ViT** β fine-tuned on Stanford Cars (8 brand classes)\n"
|
| 130 |
+
"- **CLIP** β zero-shot classification with `openai/clip-vit-large-patch14`\n"
|
| 131 |
+
"- **OpenAI GPT-4o** β vision LLM classification\n\n"
|
| 132 |
+
f"**Classes:** {', '.join(CAR_BRANDS)}"
|
| 133 |
+
)
|
| 134 |
+
|
| 135 |
+
with gr.Row():
|
| 136 |
+
input_image = gr.Image(type="filepath", label="Upload Car Image")
|
| 137 |
+
|
| 138 |
+
classify_btn = gr.Button("Classify", variant="primary")
|
| 139 |
+
|
| 140 |
+
with gr.Row():
|
| 141 |
+
with gr.Column():
|
| 142 |
+
gr.Markdown("### Custom ViT Model")
|
| 143 |
+
vit_output = gr.Label(num_top_classes=8, label="ViT Predictions")
|
| 144 |
+
with gr.Column():
|
| 145 |
+
gr.Markdown("### CLIP Zero-Shot")
|
| 146 |
+
clip_output = gr.Label(num_top_classes=8, label="CLIP Predictions")
|
| 147 |
+
with gr.Column():
|
| 148 |
+
gr.Markdown("### OpenAI GPT-4o Vision")
|
| 149 |
+
openai_output = gr.Label(num_top_classes=8, label="OpenAI Predictions")
|
| 150 |
+
|
| 151 |
+
classify_btn.click(
|
| 152 |
+
fn=classify_car,
|
| 153 |
+
inputs=[input_image],
|
| 154 |
+
outputs=[vit_output, clip_output, openai_output],
|
| 155 |
+
)
|
| 156 |
+
|
| 157 |
+
gr.Examples(
|
| 158 |
+
examples=example_images,
|
| 159 |
+
inputs=input_image,
|
| 160 |
+
label="Example Images",
|
| 161 |
+
)
|
| 162 |
+
|
| 163 |
+
demo.launch()
|
example_images/BMW.jpg
ADDED
|
Git LFS Details
|
example_images/Dodge.jpg
ADDED
|
Git LFS Details
|
example_images/Ferrari.jpg
ADDED
|
Git LFS Details
|
example_images/Porsche.jpg
ADDED
|
Git LFS Details
|
readme_.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Car Brand Classification App
|
| 2 |
+
|
| 3 |
+
This app compares 3 image classification approaches on car images:
|
| 4 |
+
|
| 5 |
+
- Fine-tuned ViT model ([`nenzilea/car-classification`](https://huggingface.co/nenzilea/car-classification))
|
| 6 |
+
- Zero-shot CLIP (`openai/clip-vit-large-patch14`)
|
| 7 |
+
- OpenAI vision model (GPT-4o image classification)
|
| 8 |
+
|
| 9 |
+
## Dataset Used For Training
|
| 10 |
+
|
| 11 |
+
- Hugging Face dataset: `tanganke/stanford_cars`
|
| 12 |
+
- The Stanford Cars dataset contains **196 fine-grained classes** (car make/model/year combinations). We group them into **8 brand-level classes** for a cleaner, more visually meaningful classification task.
|
| 13 |
+
- Number of classes: `8`
|
| 14 |
+
- Classes: `BMW`, `Ferrari`, `Ford`, `Jeep`, `Lamborghini`, `Porsche`, `Rolls-Royce`, `Toyota`
|
| 15 |
+
|
| 16 |
+
### Preprocessing Steps
|
| 17 |
+
|
| 18 |
+
1. **Brand extraction** β each of the 196 Stanford Cars class names (e.g. `"Ferrari 458 Italia Coupe 2012"`) is mapped to one of 8 brands by substring matching.
|
| 19 |
+
2. **Filtering** β images whose class does not belong to the 8 brands are removed from the dataset.
|
| 20 |
+
3. **Label remapping** β original integer labels (0β195) are re-mapped to brand indices (0β7).
|
| 21 |
+
4. **Train/validation/test split** β the original training split is divided 80/10/10 (train/validation/test) using `train_test_split(test_size=0.2, seed=42)`.
|
| 22 |
+
5. **Image preprocessing** β images are resized to 224Γ224 and pixel values are normalised to [-1, 1] using `AutoImageProcessor` from `google/vit-base-patch16-224`.
|
| 23 |
+
6. **RGB conversion** β all images are converted to RGB to handle any grayscale or RGBA edge cases.
|
| 24 |
+
|
| 25 |
+
## Trained Model
|
| 26 |
+
|
| 27 |
+
- Hugging Face model link: [https://huggingface.co/nenzilea/car-classification](https://huggingface.co/nenzilea/car-classification)
|
| 28 |
+
- Base model: `google/vit-base-patch16-224`
|
| 29 |
+
- Only the final classification head was fine-tuned (all other layers frozen).
|
| 30 |
+
- Trainable parameters: ~4,614 out of ~85.8M total.
|
| 31 |
+
|
| 32 |
+
## Training Performance
|
| 33 |
+
|
| 34 |
+
| Training Loss | Epoch | Step | Validation Loss | Accuracy |
|
| 35 |
+
|---:|---:|---:|---:|---:|
|
| 36 |
+
| β | 1.0 | β | β | β |
|
| 37 |
+
| β | 2.0 | β | β | β |
|
| 38 |
+
| β | 3.0 | β | β | β |
|
| 39 |
+
| β | 4.0 | β | β | β |
|
| 40 |
+
| β | 5.0 | β | β | β |
|
| 41 |
+
|
| 42 |
+
> Fill in the table above with the values printed by the Trainer during training.
|
| 43 |
+
|
| 44 |
+
## Hugging Face Space
|
| 45 |
+
|
| 46 |
+
- App link: [https://huggingface.co/spaces/nenzilea/car-classification](https://huggingface.co/spaces/nenzilea/car-classification)
|
| 47 |
+
|
| 48 |
+
## Example Image Results
|
| 49 |
+
|
| 50 |
+
The table below reports the true class and Top-3 predictions for ViT, CLIP, and GPT-4o.
|
| 51 |
+
|
| 52 |
+
| Image | True Class | ViT Top-3 (score) | CLIP Top-3 (score) | OpenAI GPT-4o (label, confidence) |
|
| 53 |
+
|---|---|---|---|---|
|
| 54 |
+
| `ferrari.jpg` | `Ferrari` | β | β | β |
|
| 55 |
+
| `lamborghini.jpg` | `Lamborghini` | β | β | β |
|
| 56 |
+
| `bmw.jpg` | `BMW` | β | β | β |
|
| 57 |
+
| `jeep.jpg` | `Jeep` | β | β | β |
|
| 58 |
+
| `ford.jpg` | `Ford` | β | β | β |
|
| 59 |
+
| `toyota.jpg` | `Toyota` | β | β | β |
|
| 60 |
+
| `porsche.jpg` | `Porsche` | β | β | β |
|
| 61 |
+
| `rolls_royce.jpg` | `Rolls-Royce` | β | β | β |
|
| 62 |
+
|
| 63 |
+
> Fill in the table above after running the app with the example images.
|
| 64 |
+
|
| 65 |
+
## Model Comparison Summary
|
| 66 |
+
|
| 67 |
+
| Model | Approach | Strengths | Weaknesses |
|
| 68 |
+
|---|---|---|---|
|
| 69 |
+
| **Custom ViT** | Supervised fine-tuning on 8 car brands | High accuracy on known brands | Only classifies the 8 trained brands |
|
| 70 |
+
| **CLIP** | Zero-shot with brand name as text prompt | No training needed, flexible labels | Lower accuracy; may confuse visually similar brands |
|
| 71 |
+
| **OpenAI GPT-4o** | LLM vision with natural language prompt | Strong reasoning, handles unusual angles | API cost, latency, black-box |
|
requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
transformers
|
| 2 |
+
torch
|
| 3 |
+
gradio
|
| 4 |
+
openai
|
| 5 |
+
Pillow
|