space-sue commited on
Commit
73f8435
Β·
1 Parent(s): 1d3dd21

fire and smoke alerts

Browse files
Files changed (4) hide show
  1. .gitignore +3 -0
  2. app.py +110 -67
  3. mcp_client.py +206 -0
  4. rtsp_server.py +42 -0
.gitignore CHANGED
@@ -1,2 +1,5 @@
1
  .env
2
  yolov8x-world.pt.eac99ff4aff54a2a95f4462dc49b3d49.partial
 
 
 
 
1
  .env
2
  yolov8x-world.pt.eac99ff4aff54a2a95f4462dc49b3d49.partial
3
+ fire.mp4
4
+ test.mp4
5
+ yolov8s-world.pt
app.py CHANGED
@@ -6,7 +6,7 @@ import time
6
  import os
7
  from datetime import datetime
8
  from ultralytics import YOLO
9
- from transformers import BlipProcessor, BlipForQuestionAnswering
10
  import torch
11
  import dotenv
12
  dotenv.load_dotenv()
@@ -17,44 +17,72 @@ For more information on `huggingface_hub` Inference API support, please check th
17
  client = InferenceClient("HuggingFaceH4/zephyr-7b-beta",token=os.getenv("HUGGINGFACE_HUB_TOKEN"))
18
 
19
  # Load YOLO-World model
20
- model = YOLO('yolov8x-world.pt')
21
 
22
- # Load BLIP model for VQA
23
- blip_processor = BlipProcessor.from_pretrained("Salesforce/blip-vqa-base", token=os.getenv("HUGGINGFACE_HUB_TOKEN"))
24
- vqa_model = BlipForQuestionAnswering.from_pretrained("Salesforce/blip-vqa-base", token=os.getenv("HUGGINGFACE_HUB_TOKEN"))
 
25
  device = "cuda" if torch.cuda.is_available() else "cpu"
26
  vqa_model = vqa_model.to(device)
27
 
28
  def analyze_fire_scene(frame):
29
- # Run YOLO-World inference with custom prompts
30
- results = model(frame, text=["fire", "flame", "smoke", "burning", "wildfire"])
 
 
 
 
 
 
 
 
 
31
 
32
- # Initialize detection flags and details
33
  fire_detected = False
34
  smoke_detected = False
35
  fire_details = []
36
 
37
- # Process results
38
- for result in results:
39
- boxes = result.boxes
40
- for box in boxes:
41
- confidence = float(box.conf[0])
42
- if confidence > 0.5:
43
- class_name = result.names[int(box.cls[0])]
44
- if class_name in ['fire', 'flame', 'burning', 'wildfire']:
45
- fire_detected = True
46
- # Get bounding box coordinates
47
- x1, y1, x2, y2 = box.xyxy[0].cpu().numpy()
48
- # Extract the region of interest
49
- roi = frame[int(y1):int(y2), int(x1):int(x2)]
50
- fire_details.append({
51
- 'type': class_name,
52
- 'confidence': confidence,
53
- 'location': (x1, y1, x2, y2),
54
- 'roi': roi
55
- })
56
- elif class_name == 'smoke':
57
- smoke_detected = True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
 
59
  return fire_detected, smoke_detected, fire_details
60
 
@@ -77,52 +105,67 @@ def get_fire_analysis(frame, fire_details):
77
  # out = vqa_model.generate(**inputs)
78
  # print(blip_processor.decode(out[0], skip_special_tokens=True))
79
 
80
- # Generate answer
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  with torch.no_grad():
82
- outputs = vqa_model.generate(
83
- **inputs,
84
- max_length=20,
85
- num_beams=3,
86
- min_length=1,
87
- top_p=0.9,
88
- repetition_penalty=1.5,
89
- length_penalty=1.0,
90
- temperature=1.0,
91
- )
92
- answer = blip_processor.decode(outputs[0], skip_special_tokens=True)
93
- analysis.append(f"Q: {question}\nA: {answer}")
94
 
95
  return analysis
96
 
97
- def check_for_fire():
98
- # Request webcam access
99
- cap = cv2.VideoCapture(0)
100
  if not cap.isOpened():
101
- return "Error: Could not access webcam"
102
 
103
- # Read a frame
104
- ret, frame = cap.read()
105
- if not ret:
106
- cap.release()
107
- return "Error: Could not read from webcam"
108
 
109
- # Detect fire and smoke
110
- fire_detected, smoke_detected, fire_details = analyze_fire_scene(frame)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
 
112
- # Release webcam
113
  cap.release()
114
-
115
- # Get location (you might want to implement a more sophisticated location detection)
116
- location = "Webcam Location" # Replace with actual location detection
117
-
118
- if fire_detected:
119
- # Get detailed analysis of the fire
120
- analysis = get_fire_analysis(frame, fire_details)
121
- return f"Fire detected at {location}!\n\nAnalysis:\n" + "\n".join(analysis)
122
- elif smoke_detected:
123
- return f"Smoke detected at {location}!"
124
- else:
125
- return "No fire or smoke detected"
126
 
127
  def respond(
128
  message,
@@ -134,7 +177,7 @@ def respond(
134
  ):
135
  # Check if user wants to detect fire
136
  if "detect fire" in message.lower():
137
- return check_for_fire()
138
 
139
  messages = [{"role": "system", "content": system_message}]
140
 
 
6
  import os
7
  from datetime import datetime
8
  from ultralytics import YOLO
9
+ from transformers import AutoProcessor, AutoModelForVision2Seq
10
  import torch
11
  import dotenv
12
  dotenv.load_dotenv()
 
17
  client = InferenceClient("HuggingFaceH4/zephyr-7b-beta",token=os.getenv("HUGGINGFACE_HUB_TOKEN"))
18
 
19
  # Load YOLO-World model
20
+ model = YOLO('yolov8s-world.pt')
21
 
22
+ # Load SmolVLM for VQA (lighter and better)
23
+ from transformers import AutoProcessor, AutoModelForVision2Seq
24
+ vqa_processor = AutoProcessor.from_pretrained("HuggingFaceTB/SmolVLM-Instruct", token=os.getenv("HUGGINGFACE_HUB_TOKEN"))
25
+ vqa_model = AutoModelForVision2Seq.from_pretrained("HuggingFaceTB/SmolVLM-Instruct", torch_dtype=torch.float16, token=os.getenv("HUGGINGFACE_HUB_TOKEN"))
26
  device = "cuda" if torch.cuda.is_available() else "cpu"
27
  vqa_model = vqa_model.to(device)
28
 
29
  def analyze_fire_scene(frame):
30
+ """Fast fire/smoke detection with early exit"""
31
+ from PIL import Image
32
+
33
+ # Convert frame to PIL Image
34
+ image = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
35
+
36
+ # Priority questions - most likely to detect fire/smoke quickly
37
+ questions = [
38
+ "Is there fire or flames in this image?",
39
+ "Is there smoke in this image?"
40
+ ]
41
 
 
42
  fire_detected = False
43
  smoke_detected = False
44
  fire_details = []
45
 
46
+ for question in questions:
47
+ messages = [{
48
+ "role": "user",
49
+ "content": [
50
+ {"type": "image", "image": image},
51
+ {"type": "text", "text": question}
52
+ ]
53
+ }]
54
+
55
+ prompt = vqa_processor.apply_chat_template(messages, tokenize=False)
56
+ inputs = vqa_processor(text=prompt, images=[image], return_tensors="pt")
57
+ inputs = inputs.to(device)
58
+
59
+ with torch.no_grad():
60
+ outputs = vqa_model.generate(**inputs, max_new_tokens=20, do_sample=False) # Shorter responses
61
+
62
+ answer = vqa_processor.decode(outputs[0], skip_special_tokens=True)
63
+ answer = answer.split("Assistant:")[-1].strip() if "Assistant:" in answer else answer
64
+ answer_lower = answer.lower()
65
+
66
+ # Check fire
67
+ if 'fire' in question.lower():
68
+ fire_keywords = ['fire', 'flame', 'burning', 'blaze', 'yes']
69
+ if any(word in answer_lower for word in fire_keywords):
70
+ fire_detected = True
71
+ fire_details.append({
72
+ 'type': 'fire_detected_by_vision',
73
+ 'confidence': 0.8,
74
+ 'description': answer
75
+ })
76
+ # Early exit if fire detected
77
+ return True, smoke_detected, fire_details
78
+
79
+ # Check smoke
80
+ if 'smoke' in question.lower():
81
+ smoke_keywords = ['smoke', 'smoky', 'yes']
82
+ if any(word in answer_lower for word in smoke_keywords):
83
+ smoke_detected = True
84
+ # Early exit if smoke detected
85
+ return fire_detected, True, fire_details
86
 
87
  return fire_detected, smoke_detected, fire_details
88
 
 
105
  # out = vqa_model.generate(**inputs)
106
  # print(blip_processor.decode(out[0], skip_special_tokens=True))
107
 
108
+ # Generate answer using SmolVLM
109
+ messages = [
110
+ {
111
+ "role": "user",
112
+ "content": [
113
+ {"type": "image", "image": frame},
114
+ {"type": "text", "text": question}
115
+ ]
116
+ }
117
+ ]
118
+
119
+ prompt = vqa_processor.apply_chat_template(messages, tokenize=False)
120
+ inputs = vqa_processor(text=prompt, images=[frame], return_tensors="pt")
121
+ inputs = inputs.to(device)
122
+
123
  with torch.no_grad():
124
+ outputs = vqa_model.generate(**inputs, max_new_tokens=50, do_sample=False)
125
+
126
+ answer = vqa_processor.decode(outputs[0], skip_special_tokens=True)
127
+ answer = answer.split("Assistant:")[-1].strip() if "Assistant:" in answer else answer
128
+ analysis.append(f"Q: {question}\nA: {answer}")
 
 
 
 
 
 
 
129
 
130
  return analysis
131
 
132
+ def check_for_fire(video_source=0):
133
+ """Real-time fire detection processing every 100th frame"""
134
+ cap = cv2.VideoCapture(video_source)
135
  if not cap.isOpened():
136
+ return "Error: Could not access video source"
137
 
138
+ frame_count = 0
 
 
 
 
139
 
140
+ while True:
141
+ ret, frame = cap.read()
142
+ if not ret:
143
+ break
144
+
145
+ frame_count += 1
146
+
147
+ # Process every 100th frame for real-time performance
148
+ if frame_count % 100 == 0:
149
+ print(f"Analyzing frame {frame_count}...")
150
+
151
+ # Detect fire and smoke
152
+ fire_detected, smoke_detected, fire_details = analyze_fire_scene(frame)
153
+
154
+ if fire_detected or smoke_detected:
155
+ cap.release()
156
+ location = "Video Stream"
157
+
158
+ if fire_detected:
159
+ return f"πŸ”₯ FIRE DETECTED at {location}! Frame: {frame_count}"
160
+ elif smoke_detected:
161
+ return f"πŸ’¨ SMOKE DETECTED at {location}! Frame: {frame_count}"
162
+
163
+ # Break on 'q' key (for testing)
164
+ if cv2.waitKey(1) & 0xFF == ord('q'):
165
+ break
166
 
 
167
  cap.release()
168
+ return "No fire or smoke detected in video stream"
 
 
 
 
 
 
 
 
 
 
 
169
 
170
  def respond(
171
  message,
 
177
  ):
178
  # Check if user wants to detect fire
179
  if "detect fire" in message.lower():
180
+ return check_for_fire(0) # Use webcam
181
 
182
  messages = [{"role": "system", "content": system_message}]
183
 
mcp_client.py ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import cv2
3
+ import threading
4
+ import time
5
+ import requests
6
+ import json
7
+ from datetime import datetime
8
+
9
+ class FireDetectionClient:
10
+ def __init__(self):
11
+ self.video_sources = {}
12
+ self.detection_threads = {}
13
+ self.running = {}
14
+ self.mcp_server_url = "http://localhost:7860"
15
+
16
+ def detect_fire_mcp(self, frame):
17
+ """Send frame to MCP server for fire detection"""
18
+ try:
19
+ # Convert frame to base64 or save temporarily
20
+ import base64
21
+ import io
22
+ from PIL import Image
23
+
24
+ # Convert frame to PIL Image
25
+ image = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
26
+
27
+ # Convert to base64
28
+ buffer = io.BytesIO()
29
+ image.save(buffer, format='JPEG')
30
+ img_str = base64.b64encode(buffer.getvalue()).decode()
31
+
32
+ # Send to MCP server (assuming it has an API endpoint)
33
+ response = requests.post(
34
+ f"{self.mcp_server_url}/detect_fire",
35
+ json={"image": img_str},
36
+ timeout=5
37
+ )
38
+
39
+ if response.status_code == 200:
40
+ return response.json()
41
+ else:
42
+ return {"error": "MCP server error"}
43
+
44
+ except Exception as e:
45
+ return {"error": str(e)}
46
+
47
+ def monitor_video_source(self, source_id, video_source):
48
+ """Monitor a video source for fire/smoke detection"""
49
+ cap = cv2.VideoCapture(video_source)
50
+ if not cap.isOpened():
51
+ return f"Error: Could not open video source {source_id}"
52
+
53
+ frame_count = 0
54
+ self.running[source_id] = True
55
+
56
+ while self.running.get(source_id, False):
57
+ ret, frame = cap.read()
58
+ if not ret:
59
+ break
60
+
61
+ frame_count += 1
62
+
63
+ # Process every 100th frame
64
+ if frame_count % 100 == 0:
65
+ timestamp = datetime.now().strftime("%H:%M:%S")
66
+ print(f"[{timestamp}] Source {source_id}: Analyzing frame {frame_count}")
67
+
68
+ # Simple fire detection (replace with MCP call)
69
+ fire_detected, smoke_detected = self.simple_fire_detection(frame)
70
+
71
+ if fire_detected or smoke_detected:
72
+ alert = f"🚨 ALERT - Source {source_id} at {timestamp}:\n"
73
+ if fire_detected:
74
+ alert += "πŸ”₯ FIRE DETECTED!\n"
75
+ if smoke_detected:
76
+ alert += "πŸ’¨ SMOKE DETECTED!\n"
77
+ alert += f"Frame: {frame_count}"
78
+
79
+ print(alert)
80
+ # Here you could send notifications, save alerts, etc.
81
+
82
+ time.sleep(0.01) # Small delay to prevent CPU overload
83
+
84
+ cap.release()
85
+ print(f"Stopped monitoring source {source_id}")
86
+
87
+ def simple_fire_detection(self, frame):
88
+ """Simple color-based fire detection as fallback"""
89
+ # Convert to HSV for better color detection
90
+ hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
91
+
92
+ # Fire color ranges (orange/red/yellow)
93
+ fire_lower1 = (0, 50, 50)
94
+ fire_upper1 = (10, 255, 255)
95
+ fire_lower2 = (170, 50, 50)
96
+ fire_upper2 = (180, 255, 255)
97
+
98
+ # Create masks
99
+ mask1 = cv2.inRange(hsv, fire_lower1, fire_upper1)
100
+ mask2 = cv2.inRange(hsv, fire_lower2, fire_upper2)
101
+ fire_mask = cv2.bitwise_or(mask1, mask2)
102
+
103
+ # Smoke detection (gray areas)
104
+ gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
105
+ smoke_mask = cv2.inRange(gray, 100, 200)
106
+
107
+ # Check if significant area detected
108
+ fire_area = cv2.countNonZero(fire_mask)
109
+ smoke_area = cv2.countNonZero(smoke_mask)
110
+
111
+ fire_detected = fire_area > 1000 # Threshold for fire
112
+ smoke_detected = smoke_area > 5000 # Threshold for smoke
113
+
114
+ return fire_detected, smoke_detected
115
+
116
+ def start_monitoring(self, sources):
117
+ """Start monitoring selected video sources"""
118
+ results = []
119
+
120
+ for i, source in enumerate(sources):
121
+ if source.strip():
122
+ source_id = f"Source_{i+1}"
123
+
124
+ # Convert source to appropriate format
125
+ if source.isdigit():
126
+ video_source = int(source)
127
+ else:
128
+ video_source = source
129
+
130
+ # Start monitoring thread
131
+ thread = threading.Thread(
132
+ target=self.monitor_video_source,
133
+ args=(source_id, video_source),
134
+ daemon=True
135
+ )
136
+
137
+ self.detection_threads[source_id] = thread
138
+ thread.start()
139
+
140
+ results.append(f"βœ… Started monitoring {source_id}: {source}")
141
+
142
+ return "\n".join(results) if results else "No valid sources provided"
143
+
144
+ def stop_monitoring(self):
145
+ """Stop all monitoring threads"""
146
+ for source_id in self.running:
147
+ self.running[source_id] = False
148
+
149
+ return "πŸ›‘ Stopped all monitoring"
150
+
151
+ # Initialize client
152
+ client = FireDetectionClient()
153
+
154
+ def create_interface():
155
+ """Create Gradio interface for fire detection client"""
156
+
157
+ with gr.Blocks(title="Fire Detection Client") as interface:
158
+ gr.Markdown("# πŸ”₯ Fire Detection Client")
159
+ gr.Markdown("Monitor up to 4 video sources for fire and smoke detection")
160
+
161
+ with gr.Row():
162
+ with gr.Column():
163
+ gr.Markdown("### Video Sources")
164
+ source1 = gr.Textbox(label="Source 1 (webcam: 0, file path, or RTSP URL)", placeholder="0")
165
+ source2 = gr.Textbox(label="Source 2", placeholder="rtsp://localhost:8554/stream")
166
+ source3 = gr.Textbox(label="Source 3", placeholder="C:/path/to/video.mp4")
167
+ source4 = gr.Textbox(label="Source 4", placeholder="")
168
+
169
+ with gr.Row():
170
+ start_btn = gr.Button("πŸš€ Start Monitoring", variant="primary")
171
+ stop_btn = gr.Button("πŸ›‘ Stop Monitoring", variant="secondary")
172
+
173
+ with gr.Column():
174
+ gr.Markdown("### Status")
175
+ status_output = gr.Textbox(
176
+ label="Monitoring Status",
177
+ lines=10,
178
+ interactive=False
179
+ )
180
+
181
+ gr.Markdown("### Instructions")
182
+ gr.Markdown("""
183
+ - **Webcam**: Enter `0` for default webcam, `1` for second camera
184
+ - **Video File**: Enter full path like `C:/videos/fire.mp4`
185
+ - **RTSP Stream**: Enter URL like `rtsp://localhost:8554/stream`
186
+ - **Detection**: Analyzes every 100th frame for real-time performance
187
+ - **Alerts**: Check console output for fire/smoke detection alerts
188
+ """)
189
+
190
+ # Event handlers
191
+ start_btn.click(
192
+ fn=lambda s1, s2, s3, s4: client.start_monitoring([s1, s2, s3, s4]),
193
+ inputs=[source1, source2, source3, source4],
194
+ outputs=status_output
195
+ )
196
+
197
+ stop_btn.click(
198
+ fn=client.stop_monitoring,
199
+ outputs=status_output
200
+ )
201
+
202
+ return interface
203
+
204
+ if __name__ == "__main__":
205
+ interface = create_interface()
206
+ interface.launch(server_port=7861, share=False)
rtsp_server.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import subprocess
3
+ import threading
4
+ import time
5
+
6
+ def stream_mp4_as_rtsp(mp4_file, rtsp_port=8554):
7
+ """Stream MP4 file as RTSP in a loop"""
8
+ rtsp_url = f"rtsp://localhost:{rtsp_port}/stream"
9
+
10
+ # FFmpeg command to stream MP4 as RTSP
11
+ cmd = [
12
+ 'ffmpeg',
13
+ '-re', # Read input at native frame rate
14
+ '-stream_loop', '-1', # Loop infinitely
15
+ '-i', mp4_file,
16
+ '-c', 'copy', # Copy without re-encoding
17
+ '-f', 'rtsp',
18
+ rtsp_url
19
+ ]
20
+
21
+ print(f"Starting RTSP server...")
22
+ print(f"RTSP URL: {rtsp_url}")
23
+
24
+ try:
25
+ process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
26
+ return process, rtsp_url
27
+ except Exception as e:
28
+ print(f"Error starting RTSP server: {e}")
29
+ return None, None
30
+
31
+ if __name__ == "__main__":
32
+ mp4_file = input("Enter MP4 file path: ")
33
+ process, url = stream_mp4_as_rtsp(mp4_file)
34
+
35
+ if process:
36
+ print(f"RTSP stream running at: {url}")
37
+ print("Press Ctrl+C to stop")
38
+ try:
39
+ process.wait()
40
+ except KeyboardInterrupt:
41
+ process.terminate()
42
+ print("RTSP server stopped")