Files changed (3) hide show
  1. README.md +7 -51
  2. app.py +0 -233
  3. requirements.txt +0 -15
README.md CHANGED
@@ -1,57 +1,13 @@
1
  ---
2
- title: Image Captioning
3
- emoji: 🖼️
4
- colorFrom: blue
5
- colorTo: pink
6
  sdk: gradio
7
- sdk_version: "4.40.0"
8
  app_file: app.py
9
  pinned: false
 
10
  ---
11
 
12
- # Image Captioning Hugging Face Space
13
-
14
- Triển khai inference cho mô hình image captioning bằng TensorFlow/Keras, với giao diện Gradio đơn giản cho upload ảnh và nhận caption.
15
-
16
-
17
- # Image Captioning — Hugging Face Space
18
-
19
- Triển khai inference cho mô hình image captioning dùng TensorFlow/Keras, EfficientNetV2B0 và giao diện Gradio.
20
-
21
- ## Cấu trúc tệp cần có
22
- ```text
23
- .
24
- ├── app.py # UI Gradio cho Hugging Face Space
25
- ├── flickr30k.py # Logic model + tiền xử lý (đã cung cấp)
26
- ├── best_model.keras # Trọng số mô hình (đặt cùng thư mục)
27
- ├── tokenizer.pkl # Tokenizer đã fit
28
- ├── model_config.pkl # Chứa max_length, vocab_size
29
- ├── requirements.txt
30
- └── README.md
31
- ```
32
-
33
- Các hàm sử dụng trực tiếp từ `flickr30k.py`: `load_caption_model`, `load_tokenizer_and_config`, `load_feature_extractor`, `extract_features_from_image`, `generate_caption`.
34
-
35
- ## Chạy cục bộ
36
- ```bash
37
- python -m venv .venv
38
- . .venv/bin/activate # Windows: .venv\Scripts\activate
39
- pip install --upgrade pip
40
- pip install -r requirements.txt
41
-
42
- # Đảm bảo 3 tệp đã có:
43
- # best_model.keras, tokenizer.pkl, model_config.pkl
44
-
45
- python app.py
46
- ```
47
- Mở URL Gradio hiển thị trong terminal.
48
-
49
- ## Triển khai lên Hugging Face Spaces
50
- 1) Tạo Space mới: SDK = Gradio, chọn CPU hoặc GPU tùy trọng số.
51
- 2) Đẩy các tệp: `app.py`, `flickr30k.py`, `requirements.txt`, `README.md`, và 3 tệp trọng số/cấu hình.
52
- 3) Sau khi build hoàn tất, Space sẽ mở UI upload ảnh và trả caption.
53
-
54
- ## Ghi chú tương thích
55
- - Mặc định dùng `tensorflow==2.12.0`. Nếu bạn dùng trọng số huấn luyện ở phiên bản khác, cần đồng bộ phiên bản TensorFlow/Keras tương ứng.
56
- - Sử dụng `opencv-python-headless` thay vì `opencv-python` để tránh lỗi GUI trên môi trường server.
57
- - Nếu thiếu tài nguyên trên Space Free, hạ kích thước mô hình hoặc chuyển phần cứng sang GPU trả phí.
 
1
  ---
2
+ title: Img Captioning
3
+ emoji: 📚
4
+ colorFrom: gray
5
+ colorTo: purple
6
  sdk: gradio
7
+ sdk_version: 5.44.1
8
  app_file: app.py
9
  pinned: false
10
+ short_description: Upload an image, return a caption
11
  ---
12
 
13
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app.py DELETED
@@ -1,233 +0,0 @@
1
- import os
2
- import cv2
3
- import numpy as np
4
- import pickle
5
- from PIL import Image
6
- import matplotlib.pyplot as plt
7
- import tensorflow as tf
8
- from tensorflow.keras import layers
9
- from tensorflow.keras.models import load_model, Model
10
- from tensorflow.keras.applications import EfficientNetV2B0
11
- from tensorflow.keras.applications.efficientnet import preprocess_input as efficientnet_preprocess
12
- from tensorflow.keras.preprocessing.sequence import pad_sequences
13
- from tensorflow.keras.preprocessing.image import img_to_array
14
- from tqdm import tqdm
15
- import random
16
- from tensorflow.keras.preprocessing.sequence import pad_sequences
17
-
18
- import tempfile
19
- import traceback
20
- from pathlib import Path
21
- from huggingface_hub import hf_hub_download
22
-
23
- import gradio as gr
24
- from PIL import Image
25
- import pickle
26
-
27
-
28
-
29
- # -----------------------------
30
- # Custom attention layers
31
- # -----------------------------
32
-
33
- class ChannelAttention(layers.Layer):
34
- def __init__(self, ratio=8, **kwargs):
35
- super(ChannelAttention, self).__init__(**kwargs)
36
- self.ratio = ratio
37
-
38
- def build(self, input_shape):
39
- self.gap = layers.GlobalAveragePooling1D()
40
- self.gmp = layers.GlobalMaxPooling1D()
41
- self.shared_mlp = tf.keras.Sequential([
42
- layers.Dense(units=1280 // self.ratio, activation='relu'),
43
- layers.Dense(units=1280)
44
- ])
45
- self.sigmoid = layers.Activation('sigmoid')
46
- super(ChannelAttention, self).build(input_shape)
47
-
48
- def call(self, inputs):
49
- gap = self.gap(inputs)
50
- gmp = self.gmp(inputs)
51
- gap_mlp = self.shared_mlp(gap)
52
- gmp_mlp = self.shared_mlp(gmp)
53
- channel_attention = self.sigmoid(gap_mlp + gmp_mlp)
54
- return inputs * tf.expand_dims(channel_attention, axis=1)
55
-
56
- def get_config(self):
57
- config = super(ChannelAttention, self).get_config()
58
- config.update({'ratio': self.ratio})
59
- return config
60
-
61
- @classmethod
62
- def from_config(cls, config):
63
- return cls(**config)
64
-
65
-
66
-
67
- class SpatialAttention(layers.Layer):
68
- def __init__(self, **kwargs):
69
- super(SpatialAttention, self).__init__(**kwargs)
70
-
71
- def build(self, input_shape):
72
- self.conv = layers.Conv1D(1, kernel_size=3, padding='same', activation='sigmoid')
73
- super(SpatialAttention, self).build(input_shape)
74
-
75
- def call(self, inputs):
76
- spatial_attention = self.conv(inputs)
77
- return inputs * spatial_attention
78
-
79
- def get_config(self):
80
- return super(SpatialAttention, self).get_config()
81
-
82
- @classmethod
83
- def from_config(cls, config):
84
- return cls(**config)
85
-
86
-
87
-
88
- # -----------------------------
89
- # Load model + tokenizer
90
- # -----------------------------
91
-
92
- def load_caption_model(model_path):
93
- custom_objects = {
94
- 'ChannelAttention': ChannelAttention,
95
- 'SpatialAttention': SpatialAttention
96
- }
97
- model = load_model(model_path, custom_objects=custom_objects)
98
- print("✅ Đã load model thành công!")
99
- return model
100
-
101
-
102
- def load_tokenizer_and_config(tokenizer_path, config_path):
103
- with open(tokenizer_path, 'rb') as f:
104
- tokenizer = pickle.load(f)
105
- with open(config_path, 'rb') as f:
106
- config = pickle.load(f)
107
- return tokenizer, config['max_length'], config['vocab_size']
108
-
109
-
110
- # -----------------------------
111
- # Feature extractor - EfficientNetV2B0
112
- # -----------------------------
113
-
114
- def load_feature_extractor():
115
- base_model = EfficientNetV2B0(include_top=False, weights='imagenet', pooling='avg')
116
- return Model(inputs=base_model.input, outputs=base_model.output)
117
-
118
-
119
- def extract_features_from_image(image_path, extractor):
120
- image = cv2.imread(image_path)
121
- if image is None:
122
- print(f"❌ Không đọc được ảnh: {image_path}")
123
- return None
124
- image = cv2.resize(image, (224, 224))
125
- image = img_to_array(image)
126
- image = np.expand_dims(image, axis=0)
127
- image = efficientnet_preprocess(image)
128
- feature = extractor.predict(image, verbose=0)
129
- return feature
130
-
131
-
132
- # -----------------------------
133
- # Generate caption
134
- # -----------------------------
135
-
136
- def generate_caption(model, tokenizer, image_features, max_length):
137
- in_text = 'startseq'
138
- for _ in range(max_length):
139
- sequence = tokenizer.texts_to_sequences([in_text])[0]
140
- sequence = pad_sequences([sequence], maxlen=max_length)
141
- yhat = model.predict([image_features, sequence], verbose=0)
142
- yhat = np.argmax(yhat)
143
- word = tokenizer.index_word.get(yhat)
144
- if word is None or word == 'endseq':
145
- break
146
- in_text += ' ' + word
147
- return in_text.replace('startseq ', '')
148
-
149
-
150
- # -----------------------------
151
- # Chạy test
152
- # -----------------------------
153
-
154
- MODEL_REPO = "slyviee/img_cap"
155
-
156
- # Khởi tạo tài nguyên toàn cục khi app start
157
- model_path = hf_hub_download(repo_id=MODEL_REPO, filename="best_model.keras")
158
- tokenizer_path = hf_hub_download(repo_id=MODEL_REPO, filename="tokenizer.pkl")
159
- config_path = hf_hub_download(repo_id=MODEL_REPO, filename="model_config.pkl")
160
-
161
- model = None
162
- tokenizer = None
163
- max_length = None
164
- vocab_size = None
165
- extractor = None
166
- ready = False
167
- startup_error = ""
168
-
169
-
170
- def _startup():
171
- global model, tokenizer, max_length, vocab_size, extractor, ready, startup_error
172
- try:
173
- # Kiểm tra sự tồn tại của các tệp cần thiết
174
- missing = [p for p in [model_path, tokenizer_path, config_path] if not Path(p).exists()]
175
- if missing:
176
- startup_error = "Thiếu tệp: " + ", ".join(missing)
177
- ready = False
178
- return
179
-
180
- print("🔄 Đang tải model...")
181
- model = load_caption_model(model_path)
182
- print("✅ Model đã được tải.")
183
-
184
- print("🔄 Đang tải tokenizer và config...")
185
- tokenizer, max_length, vocab_size = load_tokenizer_and_config(tokenizer_path, config_path)
186
- print("✅ Tokenizer và config đã được tải.")
187
-
188
- print("🔄 Đang tải feature extractor...")
189
- extractor = load_feature_extractor()
190
- print("✅ Feature extractor đã được tải.")
191
-
192
- ready = True
193
- except Exception as e:
194
- startup_error = f"Khởi tạo lỗi: {e}\n{traceback.format_exc()}"
195
- ready = False
196
-
197
-
198
- def predict(pil_image: Image.Image):
199
- if not ready:
200
- return f"Hệ thống chưa sẵn sàng. {startup_error or 'Thiếu model/tokenizer/config.'}"
201
-
202
- try:
203
- # Lưu ảnh tạm để tái sử dụng hàm extract_features_from_image (đọc bằng cv2)
204
- with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp:
205
- pil_image.convert("RGB").save(tmp.name, format="JPEG")
206
- tmp_path = tmp.name
207
-
208
- features = extract_features_from_image(tmp_path, extractor)
209
- os.unlink(tmp_path)
210
-
211
- if features is None:
212
- return "Không đọc được ảnh đầu vào."
213
- caption = generate_caption(model, tokenizer, features, max_length)
214
- return caption
215
- except Exception as e:
216
- return f"Lỗi trong quá trình dự đoán: {e}\n{traceback.format_exc()}"
217
-
218
- DESCRIPTION = (
219
- "Upload ảnh và nhận caption sinh ra bởi mô hình. "
220
- )
221
-
222
- demo = gr.Interface(
223
- fn=predict,
224
- inputs=gr.Image(type="pil", label="Ảnh vào"),
225
- outputs=gr.Textbox(label="Caption"),
226
- title="Image Captioning — Gradio",
227
- description=DESCRIPTION,
228
- allow_flagging="never",
229
- )
230
-
231
- if __name__ == '__main__':
232
- _startup()
233
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt DELETED
@@ -1,15 +0,0 @@
1
- # Core runtime
2
- tensorflow==2.20
3
- numpy<2
4
- pillow>=9.5.0
5
- opencv-python-headless==4.9.0.80
6
- matplotlib>=3.7.0
7
-
8
- # NLP + metrics
9
- nltk>=3.8.1
10
-
11
- # UI
12
- gradio>=4.40.0
13
-
14
- # Progress bars
15
- tqdm>=4.66.0