datbkpro commited on
Commit
4beae10
·
verified ·
1 Parent(s): 5c720e5

Update services/image_service.py

Browse files
Files changed (1) hide show
  1. services/image_service.py +283 -20
services/image_service.py CHANGED
@@ -1,33 +1,296 @@
 
 
 
 
 
 
 
1
  from groq import Groq
2
  from config.settings import settings
3
 
4
  class ImageService:
5
  def __init__(self, groq_client: Groq):
6
  self.client = groq_client
 
 
 
7
 
8
- def analyze_image_with_description(self, image, user_description: str) -> str:
9
- """Phân tích hình ảnh kết hợp với mô tả từ người dùng"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  if image is None:
11
- return "No image uploaded."
12
-
13
  try:
14
- if user_description:
15
- prompt = f"""Người dùng tải lên một hình ảnh và mô tả: "{user_description}"
16
-
17
- Dựa trên mô tả này, hãy phân tích chi tiết bằng tiếng Việt:
18
- 1. tả chi tiết những gì có trong hình ảnh
19
- 2. Phân tích các yếu tố liên quan đến mô tả của người dùng
20
- 3. Đưa ra nhận xét và thông tin hữu ích"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  else:
22
- prompt = """Hãy mô tả chi tiết bằng tiếng Việt những gì bạn nghĩ có thể có trong hình ảnh này.
23
- tả các đối tượng, màu sắc, bố cục và ngữ cảnh có thể có của hình ảnh."""
24
-
25
- chat_completion = self.client.chat.completions.create(
26
- messages=[{"role": "user", "content": prompt}],
27
- model=settings.LLM_MODEL,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  )
29
- description = chat_completion.choices[0].message.content
 
 
30
  except Exception as e:
31
- description = f"Error in image analysis: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
- return description
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+ from PIL import Image
4
+ import io
5
+ import base64
6
+ from typing import List, Dict, Any, Optional
7
+ import torch
8
  from groq import Groq
9
  from config.settings import settings
10
 
11
  class ImageService:
12
  def __init__(self, groq_client: Groq):
13
  self.client = groq_client
14
+ self.ocr_processor = None
15
+ self.easy_ocr_reader = None
16
+ self._initialize_ocr_models()
17
 
18
+ def _initialize_ocr_models(self):
19
+ """Khởi tạo các model OCR"""
20
+ try:
21
+ print("🔄 Đang khởi tạo OCR models...")
22
+
23
+ # Khởi tạo EasyOCR cho đa ngôn ngữ
24
+ try:
25
+ import easyocr
26
+ self.easy_ocr_reader = easyocr.Reader(
27
+ settings.EASYOCR_LANGUAGES,
28
+ gpu=torch.cuda.is_available()
29
+ )
30
+ print("✅ EasyOCR initialized successfully")
31
+ except ImportError:
32
+ print("❌ EasyOCR not installed, installing...")
33
+ import subprocess
34
+ subprocess.run(["pip", "install", "easyocr"])
35
+ import easyocr
36
+ self.easy_ocr_reader = easyocr.Reader(settings.EASYOCR_LANGUAGES)
37
+ print("✅ EasyOCR installed and initialized")
38
+
39
+ # Khởi tạo MangaOCR cho tiếng Việt và chữ in
40
+ try:
41
+ from manga_ocr import MangaOcr
42
+ self.ocr_processor = MangaOcr()
43
+ print("✅ MangaOCR initialized successfully")
44
+ except ImportError:
45
+ print("⚠️ MangaOCR not available, using EasyOCR only")
46
+
47
+ except Exception as e:
48
+ print(f"❌ Lỗi khởi tạo OCR: {e}")
49
+
50
+ def preprocess_image(self, image: np.ndarray) -> np.ndarray:
51
+ """Tiền xử lý ảnh để cải thiện OCR accuracy"""
52
+ try:
53
+ # Chuyển sang grayscale nếu là ảnh màu
54
+ if len(image.shape) == 3:
55
+ gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
56
+ else:
57
+ gray = image
58
+
59
+ # Áp dụng filters để cải thiện chất lượng
60
+ # Noise reduction
61
+ denoised = cv2.medianBlur(gray, 3)
62
+
63
+ # Contrast enhancement
64
+ clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
65
+ enhanced = clahe.apply(denoised)
66
+
67
+ # Thresholding
68
+ _, binary = cv2.threshold(enhanced, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
69
+
70
+ return binary
71
+
72
+ except Exception as e:
73
+ print(f"⚠️ Lỗi tiền xử lý ảnh: {e}")
74
+ return image
75
+
76
+ def extract_text_easyocr(self, image: np.ndarray) -> List[Dict[str, Any]]:
77
+ """Trích xuất text sử dụng EasyOCR"""
78
+ try:
79
+ # Tiền xử lý ảnh
80
+ processed_image = self.preprocess_image(image)
81
+
82
+ # Chuyển đổi numpy array sang PIL Image cho phù hợp
83
+ if processed_image is not None:
84
+ pil_image = Image.fromarray(processed_image)
85
+ image_np = np.array(pil_image)
86
+ else:
87
+ image_np = image
88
+
89
+ # Chạy OCR
90
+ results = self.easy_ocr_reader.readtext(
91
+ image_np,
92
+ detail=1,
93
+ paragraph=True,
94
+ contrast_ths=0.3,
95
+ adjust_contrast=0.7
96
+ )
97
+
98
+ # Format kết quả
99
+ extracted_texts = []
100
+ for bbox, text, confidence in results:
101
+ extracted_texts.append({
102
+ 'text': text,
103
+ 'confidence': float(confidence),
104
+ 'bbox': bbox
105
+ })
106
+
107
+ return extracted_texts
108
+
109
+ except Exception as e:
110
+ print(f"❌ Lỗi EasyOCR: {e}")
111
+ return []
112
+
113
+ def extract_text_mangaocr(self, image: np.ndarray) -> List[Dict[str, Any]]:
114
+ """Trích xuất text sử dụng MangaOCR (tốt cho tiếng Việt)"""
115
+ try:
116
+ if self.ocr_processor is None:
117
+ return []
118
+
119
+ # Chuyển numpy array sang PIL Image
120
+ pil_image = Image.fromarray(image)
121
+
122
+ # Chạy OCR
123
+ text = self.ocr_processor(pil_image)
124
+
125
+ return [{
126
+ 'text': text,
127
+ 'confidence': 0.9, # MangaOCR không trả về confidence
128
+ 'bbox': None,
129
+ 'source': 'manga_ocr'
130
+ }]
131
+
132
+ except Exception as e:
133
+ print(f"❌ Lỗi MangaOCR: {e}")
134
+ return []
135
+
136
+ def merge_ocr_results(self, easyocr_results: List, mangaocr_results: List) -> str:
137
+ """Kết hợp và chọn lọc kết quả từ nhiều OCR engine"""
138
+ all_texts = []
139
+
140
+ # Ưu tiên kết quả từ EasyOCR với confidence cao
141
+ for result in easyocr_results:
142
+ if result['confidence'] > 0.5: # Ngưỡng confidence
143
+ all_texts.append(result['text'])
144
+
145
+ # Thêm kết quả từ MangaOCR nếu có
146
+ for result in mangaocr_results:
147
+ all_texts.append(result['text'])
148
+
149
+ # Loại bỏ trùng lặp và kết hợp
150
+ unique_texts = []
151
+ seen_texts = set()
152
+
153
+ for text in all_texts:
154
+ clean_text = text.strip()
155
+ if clean_text and len(clean_text) > 1 and clean_text not in seen_texts:
156
+ unique_texts.append(clean_text)
157
+ seen_texts.add(clean_text)
158
+
159
+ return "\n".join(unique_texts) if unique_texts else "Không phát hiện được văn bản trong ảnh."
160
+
161
+ def extract_text_from_image(self, image: np.ndarray) -> str:
162
+ """Trích xuất văn bản từ ảnh sử dụng nhiều OCR engine"""
163
  if image is None:
164
+ return "Không ảnh được tải lên."
165
+
166
  try:
167
+ print("🔍 Đang trích xuất văn bản từ ảnh...")
168
+
169
+ # Chạy cả hai OCR engine
170
+ easyocr_results = self.extract_text_easyocr(image)
171
+ mangaocr_results = self.extract_text_mangaocr(image)
172
+
173
+ print(f"📊 EasyOCR found {len(easyocr_results)} text regions")
174
+ print(f"📊 MangaOCR found {len(mangaocr_results)} text regions")
175
+
176
+ # Kết hợp kết quả
177
+ merged_text = self.merge_ocr_results(easyocr_results, mangaocr_results)
178
+
179
+ print(f"✅ Extracted text: {merged_text[:100]}...")
180
+ return merged_text
181
+
182
+ except Exception as e:
183
+ print(f"❌ Lỗi trích xuất văn bản: {e}")
184
+ return f"Lỗi khi trích xuất văn bản: {str(e)}"
185
+
186
+ def analyze_text_with_llm(self, extracted_text: str, user_description: str = "") -> str:
187
+ """Phân tích văn bản trích xuất được bằng LLM"""
188
+ try:
189
+ if not extracted_text or extracted_text == "Không phát hiện được văn bản trong ảnh.":
190
+ prompt = """
191
+ Tôi đã tải lên một hình ảnh nhưng không thể trích xuất được văn bản từ đó.
192
+ Hãy mô tả tổng quan về hình ảnh này và đưa ra các phán đoán về nội dung có thể có.
193
+ """
194
  else:
195
+ if user_description:
196
+ prompt = f"""
197
+ NGƯỜI DÙNG MÔ TẢ: "{user_description}"
198
+
199
+ VĂN BẢN TRÍCH XUẤT TỪ ẢNH:
200
+ {extracted_text}
201
+
202
+ Dựa trên mô tả của người dùng và văn bản trích xuất được, hãy:
203
+ 1. Phân tích và tóm tắt nội dung chính
204
+ 2. Giải thích ý nghĩa của văn bản trong ngữ cảnh
205
+ 3. Đưa ra thông tin bổ sung hữu ích
206
+ """
207
+ else:
208
+ prompt = f"""
209
+ VĂN BẢN TRÍCH XUẤT TỪ ẢNH:
210
+ {extracted_text}
211
+
212
+ Hãy phân tích và cung cấp thông tin về:
213
+ 1. Nội dung chính của văn bản
214
+ 2. Loại văn bản (tài liệu, quảng cáo, tin nhắn, etc.)
215
+ 3. Ngữ cảnh và ý nghĩa
216
+ 4. Thông tin hữu ích khác
217
+ """
218
+
219
+ # Gọi LLM để phân tích
220
+ completion = self.client.chat.completions.create(
221
+ model=settings.DEFAULT_LLM_MODEL,
222
+ messages=[
223
+ {
224
+ "role": "system",
225
+ "content": "Bạn là trợ lý phân tích hình ảnh và văn bản. Hãy trả lời bằng tiếng Việt tự nhiên, rõ ràng và hữu ích."
226
+ },
227
+ {
228
+ "role": "user",
229
+ "content": prompt
230
+ }
231
+ ],
232
+ max_tokens=500,
233
+ temperature=0.7
234
  )
235
+
236
+ return completion.choices[0].message.content
237
+
238
  except Exception as e:
239
+ print(f" Lỗi phân tích với LLM: {e}")
240
+ return f"Lỗi khi phân tích với AI: {str(e)}"
241
+
242
+ def analyze_image_with_description(self, image, user_description: str = "") -> str:
243
+ """Phân tích ảnh hoàn chỉnh: OCR + LLM"""
244
+ if image is None:
245
+ return "❌ Vui lòng tải lên một hình ảnh để phân tích."
246
+
247
+ try:
248
+ # Bước 1: Trích xuất văn bản từ ảnh
249
+ extracted_text = self.extract_text_from_image(image)
250
+
251
+ # Bước 2: Phân tích với LLM
252
+ analysis_result = self.analyze_text_with_llm(extracted_text, user_description)
253
+
254
+ # Format kết quả cuối cùng
255
+ result = f"""📊 **KẾT QUẢ PHÂN TÍCH HÌNH ẢNH**
256
 
257
+ 🔍 **Văn bản trích xuất được:**
258
+ {extracted_text}
259
+
260
+ 🤖 **Phân tích AI:**
261
+ {analysis_result}
262
+
263
+ ---
264
+ *Phân tích sử dụng OCR và AI - Độ chính xác có thể thay đổi tùy thuộc vào chất lượng ảnh.*"""
265
+
266
+ return result
267
+
268
+ except Exception as e:
269
+ print(f"❌ Lỗi phân tích ảnh: {e}")
270
+ return f"❌ Lỗi trong quá trình phân tích: {str(e)}"
271
+
272
+ def detect_image_type(self, image: np.ndarray) -> str:
273
+ """Nhận diện loại ảnh (tài liệu, ảnh chụp, meme, etc.)"""
274
+ try:
275
+ # Phân tích đơn giản dựa trên đặc điểm ảnh
276
+ height, width = image.shape[:2]
277
+ aspect_ratio = width / height
278
+
279
+ # Phân tích màu sắc
280
+ if len(image.shape) == 3:
281
+ color_variance = np.var(image, axis=(0,1))
282
+ is_colorful = np.mean(color_variance) > 100
283
+ else:
284
+ is_colorful = False
285
+
286
+ # Phân tích sơ bộ loại ảnh
287
+ if aspect_ratio > 2.0 or aspect_ratio < 0.5:
288
+ return "document" # Tài liệu thường có tỷ lệ khác thường
289
+ elif not is_colorful:
290
+ return "document" # Ít màu sắc -> có thể là tài liệu
291
+ else:
292
+ return "photo" # Ảnh chụp
293
+
294
+ except Exception as e:
295
+ print(f"⚠️ Lỗi nhận diện loại ảnh: {e}")
296
+ return "unknown"