Spaces:
Running
Running
Upload 7 files
Browse files- app.py +197 -0
- cctv_agent.py +68 -0
- deepseek_agent.py +126 -0
- last.pt +3 -0
- mail_agent.py +64 -0
- requirements.txt +8 -0
- yolo_agent.py +160 -0
app.py
ADDED
@@ -0,0 +1,197 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import sys
|
3 |
+
import gradio as gr
|
4 |
+
|
5 |
+
from yolo_agent import video_detection_tool
|
6 |
+
from deepseek_agent import analysis_tool
|
7 |
+
from mail_agent import mail_tool
|
8 |
+
from cctv_agent import cctv_detection_tool
|
9 |
+
|
10 |
+
def cli_pipeline():
|
11 |
+
"""
|
12 |
+
|
13 |
+
"""
|
14 |
+
video_path = input("Enter the full path to the video file: ").strip()
|
15 |
+
if not os.path.exists(video_path):
|
16 |
+
print("The specified file was not found!")
|
17 |
+
return
|
18 |
+
|
19 |
+
# 1. YOLO Detection
|
20 |
+
with open(video_path, 'rb') as video_file:
|
21 |
+
detection_result = video_detection_tool({"video": video_file})
|
22 |
+
print("Detection Result:\n", detection_result)
|
23 |
+
|
24 |
+
# 2. DeepSeek Analysis
|
25 |
+
detections_folder = "detections"
|
26 |
+
detections_file = os.path.join(detections_folder, "detections.txt")
|
27 |
+
if not os.path.exists(detections_file):
|
28 |
+
print("Detections file not found! Please check the detection process first.")
|
29 |
+
return
|
30 |
+
|
31 |
+
analysis_result = analysis_tool(detections_file)
|
32 |
+
print("Analysis Result:\n", analysis_result)
|
33 |
+
|
34 |
+
# 3. Email Sending (optional)
|
35 |
+
analysis_file = os.path.join(detections_folder, "analysis.txt")
|
36 |
+
if not os.path.exists(analysis_file):
|
37 |
+
print("analysis.txt not found; email cannot be sent.")
|
38 |
+
return
|
39 |
+
|
40 |
+
image_path = os.path.join(detections_folder, "1.png")
|
41 |
+
if not os.path.exists(image_path):
|
42 |
+
print("1.png not found; email will not be sent.")
|
43 |
+
return
|
44 |
+
|
45 |
+
sender_email = input("Enter the sender email address: ").strip()
|
46 |
+
sender_password = input("Enter the email application password: ").strip()
|
47 |
+
recipient_email = input("Enter the recipient email address: ").strip()
|
48 |
+
|
49 |
+
mail_result = mail_tool({
|
50 |
+
"input_data": {
|
51 |
+
"analysis_file": analysis_file,
|
52 |
+
"image_file": image_path,
|
53 |
+
"sender_email": sender_email,
|
54 |
+
"sender_password": sender_password,
|
55 |
+
"recipient_email": recipient_email
|
56 |
+
}
|
57 |
+
})
|
58 |
+
print("Email Result:\n", mail_result)
|
59 |
+
|
60 |
+
|
61 |
+
def process_input(mode, video_file, rtsp_url, model_selection, user_id, sender_email, sender_password, recipient_email):
|
62 |
+
"""
|
63 |
+
Processes inputs from the Gradio interface:
|
64 |
+
- In "Upload Video" mode: runs YOLO detection via video_detection_tool.
|
65 |
+
- In "Connect to CCTV" mode: runs detection via cctv_detection_tool using the provided RTSP URL.
|
66 |
+
|
67 |
+
In video mode, if detection and analysis are successful, it uses the email settings
|
68 |
+
provided by the user to send an email.
|
69 |
+
"""
|
70 |
+
if mode == "Upload Video":
|
71 |
+
if video_file is None:
|
72 |
+
return "Please upload a video file."
|
73 |
+
detection_result = video_detection_tool({"video": video_file})
|
74 |
+
detections_file = os.path.join("detections", "detections.txt")
|
75 |
+
if not os.path.exists(detections_file):
|
76 |
+
return detection_result + "\n\nDetections file not found."
|
77 |
+
analysis_result = analysis_tool(detections_file)
|
78 |
+
image_path = os.path.join("detections", "1.png")
|
79 |
+
if os.path.exists(image_path):
|
80 |
+
if not sender_email or not sender_password or not recipient_email:
|
81 |
+
mail_part = "Missing email information for sending the email."
|
82 |
+
else:
|
83 |
+
mail_part = mail_tool({
|
84 |
+
"input_data": {
|
85 |
+
"analysis_file": os.path.join("detections", "analysis.txt"),
|
86 |
+
"image_file": image_path,
|
87 |
+
"sender_email": sender_email,
|
88 |
+
"sender_password": sender_password,
|
89 |
+
"recipient_email": recipient_email
|
90 |
+
}
|
91 |
+
})
|
92 |
+
return f"{detection_result}\n\n{analysis_result}\n\n{mail_part}"
|
93 |
+
else:
|
94 |
+
return f"{detection_result}\n\n{analysis_result}\n\n1.png not found, email not sent."
|
95 |
+
|
96 |
+
elif mode == "Connect to CCTV":
|
97 |
+
if not rtsp_url or rtsp_url.strip() == "":
|
98 |
+
return "Please enter a valid RTSP URL."
|
99 |
+
detection_result = cctv_detection_tool(rtsp_url, model_selection)
|
100 |
+
return detection_result
|
101 |
+
|
102 |
+
else:
|
103 |
+
return "Invalid mode selected."
|
104 |
+
|
105 |
+
|
106 |
+
def launch_gradio_interface():
|
107 |
+
"""
|
108 |
+
Launches the Gradio interface.
|
109 |
+
Users can choose either "Upload Video" or "Connect to CCTV" mode and provide the necessary inputs
|
110 |
+
to perform detection, analysis, and optionally email sending.
|
111 |
+
"""
|
112 |
+
mode_choices = ["Upload Video", "Connect to CCTV"]
|
113 |
+
model_choices = ["last.pt"]
|
114 |
+
|
115 |
+
with gr.Blocks(css=".gradio-container { font-family: 'Segoe UI', sans-serif; }") as demo:
|
116 |
+
gr.Markdown("## YOLO Detection: Video or CCTV Stream\n"
|
117 |
+
"In this interface, you can either upload your local video or connect via a CCTV RTSP stream "
|
118 |
+
"to perform object detection.\n"
|
119 |
+
"**Additionally:** DeepSeek analysis and email sending are integrated.")
|
120 |
+
gr.Markdown("### 1) Mode Selection")
|
121 |
+
mode = gr.Radio(mode_choices, label="How would you like to perform the detection?", value="Upload Video")
|
122 |
+
gr.Markdown("---")
|
123 |
+
|
124 |
+
with gr.Row():
|
125 |
+
with gr.Column():
|
126 |
+
gr.Markdown("**Local Video File**")
|
127 |
+
video_input = gr.Video(
|
128 |
+
label="Upload Video",
|
129 |
+
sources="upload",
|
130 |
+
visible=True
|
131 |
+
)
|
132 |
+
with gr.Column():
|
133 |
+
gr.Markdown("**CCTV Stream (RTSP)**")
|
134 |
+
rtsp_input = gr.Textbox(
|
135 |
+
label="RTSP URL",
|
136 |
+
placeholder="rtsp://username:password@ip:port/stream",
|
137 |
+
visible=False
|
138 |
+
)
|
139 |
+
gr.Markdown("---")
|
140 |
+
with gr.Row():
|
141 |
+
model_selection_input = gr.Dropdown(
|
142 |
+
model_choices,
|
143 |
+
label="YOLO Model Selection",
|
144 |
+
value="last.pt",
|
145 |
+
info="Select the YOLO model you wish to use."
|
146 |
+
)
|
147 |
+
user_id = gr.Textbox(
|
148 |
+
label="User ID (Optional)",
|
149 |
+
placeholder="Enter your user ID..."
|
150 |
+
)
|
151 |
+
gr.Markdown("### Email Information (for sending results)")
|
152 |
+
with gr.Row():
|
153 |
+
sender_email = gr.Textbox(
|
154 |
+
label="Sender Email Address",
|
155 |
+
placeholder="e.g., [email protected]"
|
156 |
+
)
|
157 |
+
sender_password = gr.Textbox(
|
158 |
+
label="Email (app) Password",
|
159 |
+
placeholder="Enter your password",
|
160 |
+
type="password"
|
161 |
+
)
|
162 |
+
recipient_email = gr.Textbox(
|
163 |
+
label="Recipient Email Address",
|
164 |
+
placeholder="e.g., [email protected]"
|
165 |
+
)
|
166 |
+
gr.Markdown("---")
|
167 |
+
output_text = gr.Textbox(
|
168 |
+
label="Output",
|
169 |
+
interactive=False,
|
170 |
+
placeholder="Detection, analysis, and email output will be shown here..."
|
171 |
+
)
|
172 |
+
|
173 |
+
def update_visibility(selected_mode):
|
174 |
+
if selected_mode == "Upload Video":
|
175 |
+
return gr.update(visible=True), gr.update(visible=False)
|
176 |
+
else:
|
177 |
+
return gr.update(visible=False), gr.update(visible=True)
|
178 |
+
|
179 |
+
mode.change(fn=update_visibility, inputs=mode, outputs=[video_input, rtsp_input])
|
180 |
+
submit_btn = gr.Button("Start Detection", variant="primary")
|
181 |
+
submit_btn.click(fn=process_input,
|
182 |
+
inputs=[mode, video_input, rtsp_input, model_selection_input, user_id, sender_email, sender_password, recipient_email],
|
183 |
+
outputs=output_text)
|
184 |
+
gr.Markdown("---\n"
|
185 |
+
"bahakizil\n")
|
186 |
+
demo.launch(server_name="0.0.0.0", server_port=7860)
|
187 |
+
|
188 |
+
|
189 |
+
def main():
|
190 |
+
cli_pipeline()
|
191 |
+
|
192 |
+
|
193 |
+
if __name__ == "__main__":
|
194 |
+
if len(sys.argv) > 1 and sys.argv[1] == "--cli":
|
195 |
+
main()
|
196 |
+
else:
|
197 |
+
launch_gradio_interface()
|
cctv_agent.py
ADDED
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
### START OF cctv_agent.py ###
|
2 |
+
import os
|
3 |
+
import cv2
|
4 |
+
from ultralytics import YOLO
|
5 |
+
from langchain.agents import tool
|
6 |
+
|
7 |
+
@tool("cctv_detection_tool", return_direct=True)
|
8 |
+
def cctv_detection_tool(rtsp_url: str, model_path: str = None, output_dir: str = "detections", frame_skip: int = 30, conf: float = 0.75) -> str:
|
9 |
+
"""
|
10 |
+
RTSP URL'si verilen CCTV kamerasına bağlanarak YOLO ile canlı nesne tespiti yapar.
|
11 |
+
|
12 |
+
Args:
|
13 |
+
rtsp_url (str): CCTV RTSP bağlantı URL'si.
|
14 |
+
model_path (str, optional): YOLO model dosyasının yolu. Belirtilmezse yolo_agent.py içindeki default (last.pt) kullanılır.
|
15 |
+
output_dir (str): Tespit çıktı dosyalarının kaydedileceği dizin.
|
16 |
+
frame_skip (int): İşlenecek kareler arasında atlanacak kare sayısı.
|
17 |
+
conf (float): Tespit için güven eşiği.
|
18 |
+
|
19 |
+
Returns:
|
20 |
+
str: İşlemin sonucuna dair özet mesaj.
|
21 |
+
"""
|
22 |
+
if model_path is None:
|
23 |
+
model_path = os.path.join(os.path.dirname(__file__), "yolo", "last.pt")
|
24 |
+
cap = cv2.VideoCapture(rtsp_url)
|
25 |
+
if not cap.isOpened():
|
26 |
+
return f"RTSP akışı açılamadı: {rtsp_url}"
|
27 |
+
os.makedirs(output_dir, exist_ok=True)
|
28 |
+
output_txt = os.path.join(output_dir, "cctv_detections.txt")
|
29 |
+
ftxt = open(output_txt, "w")
|
30 |
+
try:
|
31 |
+
model = YOLO(model_path)
|
32 |
+
except Exception as e:
|
33 |
+
return f"YOLO modeli yüklenemedi: {e}"
|
34 |
+
frame_count = 0
|
35 |
+
saved_image_index = 1
|
36 |
+
while cap.isOpened():
|
37 |
+
ret, frame = cap.read()
|
38 |
+
if not ret:
|
39 |
+
break
|
40 |
+
results = model(frame, conf=conf)
|
41 |
+
detections = results[0].boxes.data.cpu().numpy() if len(results) > 0 else []
|
42 |
+
valid_detections = [det for det in detections if int(det[5]) in [0, 1, 2, 3, 4, 5]]
|
43 |
+
if valid_detections:
|
44 |
+
for det in valid_detections:
|
45 |
+
x1, y1, x2, y2, conf_score, cls_ = det
|
46 |
+
class_id = int(cls_)
|
47 |
+
if class_id in [0, 1, 2]:
|
48 |
+
class_label = "Danger"
|
49 |
+
elif class_id in [3, 4, 5]:
|
50 |
+
class_label = model.names.get(class_id, f"Class {class_id}")
|
51 |
+
else:
|
52 |
+
class_label = f"Class {class_id}"
|
53 |
+
cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 0, 255), 3)
|
54 |
+
ftxt.write(f"Frame {frame_count}: {class_label} at ({int(x1)}, {int(y1)}, {int(x2)}, {int(y2)})\n")
|
55 |
+
# Örnek olarak bir kareyi kaydet
|
56 |
+
output_frame_path = os.path.join(output_dir, f"cctv_{saved_image_index}.png")
|
57 |
+
cv2.imwrite(output_frame_path, frame)
|
58 |
+
saved_image_index += 1
|
59 |
+
frame_count += frame_skip
|
60 |
+
cap.set(cv2.CAP_PROP_POS_FRAMES, frame_count)
|
61 |
+
# Demo amacıyla belirli sayıda kare işlendikten sonra döngüden çıkıyoruz
|
62 |
+
if frame_count > 300:
|
63 |
+
break
|
64 |
+
ftxt.close()
|
65 |
+
cap.release()
|
66 |
+
cv2.destroyAllWindows()
|
67 |
+
return f"CCTV tespiti tamamlandı. Çıktılar '{output_dir}' dizininde; log: '{output_txt}'."
|
68 |
+
### END OF cctv_agent.py ###
|
deepseek_agent.py
ADDED
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import re
|
3 |
+
from langchain.tools import tool
|
4 |
+
from langchain_ollama import ChatOllama
|
5 |
+
|
6 |
+
@tool("analysis_tool", return_direct=True)
|
7 |
+
def analysis_tool(detections_file: str) -> str:
|
8 |
+
"""
|
9 |
+
Reads the YOLO detection log (detections_file) and uses the DeepSeek model via ChatOllama
|
10 |
+
to produce an email-style summary in plain text with:
|
11 |
+
|
12 |
+
- Number of 'Danger' detections
|
13 |
+
- Brief explanation
|
14 |
+
- General comment/warning
|
15 |
+
|
16 |
+
The final text is written to 'analysis.txt', no chain-of-thought or placeholders.
|
17 |
+
"""
|
18 |
+
if not os.path.exists(detections_file):
|
19 |
+
return f"detections_file not found: {detections_file}"
|
20 |
+
|
21 |
+
# Read YOLO detection log
|
22 |
+
with open(detections_file, "r") as f:
|
23 |
+
detections_text = f.read().strip()
|
24 |
+
|
25 |
+
# Count Danger occurrences
|
26 |
+
manual_count = detections_text.count("Danger")
|
27 |
+
|
28 |
+
# Build system and human prompts
|
29 |
+
system_prompt = (
|
30 |
+
"You are an AI that summarizes YOLO detection logs. "
|
31 |
+
"Return only the final summary with 3 bullet lines:\n"
|
32 |
+
"1) - Number of 'Danger' detections: X\n"
|
33 |
+
"2) - Brief explanation: ...\n"
|
34 |
+
"3) - General comment/warning: ...\n"
|
35 |
+
"No chain of thought, no subject, no email headers. Output ONLY the body lines in plain text."
|
36 |
+
)
|
37 |
+
|
38 |
+
human_prompt = f"""
|
39 |
+
YOLO detection log:
|
40 |
+
{detections_text}
|
41 |
+
|
42 |
+
Please produce exactly 3 bullet lines:
|
43 |
+
- Number of 'Danger' detections: <number>
|
44 |
+
- Brief explanation: <one line summary>
|
45 |
+
- General comment/warning: <safety note>
|
46 |
+
"""
|
47 |
+
|
48 |
+
llm = ChatOllama(
|
49 |
+
model="deepseek-r1:1.5b",
|
50 |
+
temperature=0.0,
|
51 |
+
base_url="http://localhost:11434",
|
52 |
+
)
|
53 |
+
|
54 |
+
messages = [
|
55 |
+
("system", system_prompt),
|
56 |
+
("human", human_prompt),
|
57 |
+
]
|
58 |
+
|
59 |
+
# Get raw model output
|
60 |
+
ai_msg = llm.invoke(messages)
|
61 |
+
raw_output = ai_msg.content.strip()
|
62 |
+
|
63 |
+
# Remove <think> blocks
|
64 |
+
raw_output = re.sub(r"<think>.*?</think>", "", raw_output, flags=re.DOTALL)
|
65 |
+
|
66 |
+
# Split lines
|
67 |
+
lines = [line.strip() for line in raw_output.splitlines() if line.strip()]
|
68 |
+
|
69 |
+
# We'll look for the bullet lines
|
70 |
+
danger_idx = None
|
71 |
+
explain_idx = None
|
72 |
+
comment_idx = None
|
73 |
+
|
74 |
+
for i, line in enumerate(lines):
|
75 |
+
lower_line = line.lower()
|
76 |
+
if "number of 'danger' detections:" in lower_line:
|
77 |
+
danger_idx = i
|
78 |
+
elif "brief explanation:" in lower_line:
|
79 |
+
explain_idx = i
|
80 |
+
elif "general comment/warning:" in lower_line:
|
81 |
+
comment_idx = i
|
82 |
+
|
83 |
+
final_lines = lines[:]
|
84 |
+
|
85 |
+
# 1) If Danger line doesn't exist, append fallback
|
86 |
+
if danger_idx is None:
|
87 |
+
final_lines.append(f"- Number of 'Danger' detections: {manual_count}")
|
88 |
+
else:
|
89 |
+
# If it exists but has placeholder <number>, replace it
|
90 |
+
line_text = final_lines[danger_idx]
|
91 |
+
if "<number>" in line_text:
|
92 |
+
new_line = line_text.replace("<number>", str(manual_count))
|
93 |
+
final_lines[danger_idx] = new_line
|
94 |
+
|
95 |
+
# 2) Explanation line fallback
|
96 |
+
if explain_idx is None:
|
97 |
+
if manual_count == 0:
|
98 |
+
final_lines.append("- Brief explanation: No Danger found.")
|
99 |
+
else:
|
100 |
+
final_lines.append("- Brief explanation: Potential risks detected.")
|
101 |
+
else:
|
102 |
+
# If it has <one line summary> placeholder
|
103 |
+
if "<one line summary>" in final_lines[explain_idx]:
|
104 |
+
final_lines[explain_idx] = final_lines[explain_idx].replace(
|
105 |
+
"<one line summary>",
|
106 |
+
"Potential risks detected." if manual_count else "No Danger found."
|
107 |
+
)
|
108 |
+
|
109 |
+
# 3) Comment line fallback
|
110 |
+
if comment_idx is None:
|
111 |
+
final_lines.append("- General comment/warning: Review and investigate for safety issues.")
|
112 |
+
else:
|
113 |
+
if "<safety note>" in final_lines[comment_idx]:
|
114 |
+
final_lines[comment_idx] = final_lines[comment_idx].replace(
|
115 |
+
"<safety note>", "Please monitor closely." # or custom
|
116 |
+
)
|
117 |
+
|
118 |
+
# Join final lines
|
119 |
+
final_output = "\n".join(final_lines)
|
120 |
+
|
121 |
+
# Save to analysis.txt
|
122 |
+
analysis_path = os.path.join(os.path.dirname(detections_file), "analysis.txt")
|
123 |
+
with open(analysis_path, "w") as out_f:
|
124 |
+
out_f.write(final_output)
|
125 |
+
|
126 |
+
return f"Analysis complete. Summary saved to '{analysis_path}'."
|
last.pt
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:3c7f69ad83e71c4a19ffbcdff30fcabbbbee4ef335033c8fb24b35186d0f38ce
|
3 |
+
size 5470931
|
mail_agent.py
ADDED
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from langchain.tools import tool
|
2 |
+
import os
|
3 |
+
import smtplib
|
4 |
+
from email.mime.text import MIMEText
|
5 |
+
from email.mime.multipart import MIMEMultipart
|
6 |
+
from email.mime.base import MIMEBase
|
7 |
+
from email import encoders
|
8 |
+
|
9 |
+
@tool("mail_tool", return_direct=True)
|
10 |
+
def mail_tool(input_data: dict) -> str:
|
11 |
+
"""
|
12 |
+
Expects a dict parameter with the following keys:
|
13 |
+
- analysis_file: Path to the analysis.txt file.
|
14 |
+
- image_file: Path to an image file (e.g., 1.png) to attach.
|
15 |
+
- sender_email: The email address from which to send the email.
|
16 |
+
- sender_password: The password or app-specific password for the sender email.
|
17 |
+
- recipient_email: The email address to send the email to.
|
18 |
+
|
19 |
+
This function reads the analysis report, attaches the image, and sends an email using the provided credentials.
|
20 |
+
"""
|
21 |
+
analysis_file = input_data.get("analysis_file", "")
|
22 |
+
image_file = input_data.get("image_file", "")
|
23 |
+
sender_email = input_data.get("sender_email", "")
|
24 |
+
sender_password = input_data.get("sender_password", "")
|
25 |
+
recipient_email = input_data.get("recipient_email", "")
|
26 |
+
|
27 |
+
if not os.path.exists(analysis_file):
|
28 |
+
return f"Analysis file not found: {analysis_file}"
|
29 |
+
|
30 |
+
with open(analysis_file, "r") as f:
|
31 |
+
email_body = f.read().strip()
|
32 |
+
|
33 |
+
SMTP_SERVER = "smtp.gmail.com"
|
34 |
+
SMTP_PORT = 587
|
35 |
+
|
36 |
+
msg = MIMEMultipart()
|
37 |
+
msg["Subject"] = "DeepSeek Analysis Report"
|
38 |
+
msg["From"] = sender_email
|
39 |
+
msg["To"] = recipient_email
|
40 |
+
msg.attach(MIMEText(email_body, "plain"))
|
41 |
+
|
42 |
+
if image_file and os.path.isfile(image_file):
|
43 |
+
try:
|
44 |
+
with open(image_file, "rb") as attachment:
|
45 |
+
part = MIMEBase("application", "octet-stream")
|
46 |
+
part.set_payload(attachment.read())
|
47 |
+
encoders.encode_base64(part)
|
48 |
+
part.add_header("Content-Disposition", f'attachment; filename={os.path.basename(image_file)}')
|
49 |
+
msg.attach(part)
|
50 |
+
except Exception as e:
|
51 |
+
return f"Error attaching {image_file}: {e}"
|
52 |
+
else:
|
53 |
+
return f"No image file found at '{image_file}'. Email not sent."
|
54 |
+
|
55 |
+
try:
|
56 |
+
with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as server:
|
57 |
+
server.ehlo()
|
58 |
+
server.starttls()
|
59 |
+
server.login(sender_email, sender_password)
|
60 |
+
server.send_message(msg)
|
61 |
+
except Exception as e:
|
62 |
+
return f"Failed to send email: {str(e)}"
|
63 |
+
|
64 |
+
return f"Email sent successfully to {recipient_email}."
|
requirements.txt
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
langchain
|
2 |
+
langchain_ollama
|
3 |
+
ultralytics
|
4 |
+
opencv-python
|
5 |
+
transformers
|
6 |
+
accelerate
|
7 |
+
torch
|
8 |
+
torchvision
|
yolo_agent.py
ADDED
@@ -0,0 +1,160 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
LangChain YOLO Agent
|
3 |
+
---------------------
|
4 |
+
|
5 |
+
This project provides a YOLO-based object detection tool integrated with LangChain.
|
6 |
+
Users can upload any video to analyze its contents, generate object detection logs,
|
7 |
+
and visualize detections with bounding boxes.
|
8 |
+
|
9 |
+
Steps:
|
10 |
+
1) Install dependencies: `pip install langchain openai ultralytics opencv-python`
|
11 |
+
2) Add this file (`yolo_agent.py`) to your project.
|
12 |
+
3) Ensure that the YOLO model file (`last.pt`) is available in the working directory.
|
13 |
+
4) Use the provided functions to analyze uploaded videos dynamically.
|
14 |
+
"""
|
15 |
+
|
16 |
+
import os
|
17 |
+
import cv2
|
18 |
+
import shutil
|
19 |
+
from langchain.agents import Tool, tool
|
20 |
+
from ultralytics import YOLO
|
21 |
+
|
22 |
+
UPLOAD_FOLDER = "uploads"
|
23 |
+
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
24 |
+
|
25 |
+
MODEL_PATH = os.path.join(os.path.dirname(__file__), "last.pt")
|
26 |
+
|
27 |
+
def detect_with_yolo(
|
28 |
+
video_path: str,
|
29 |
+
model_path: str = MODEL_PATH,
|
30 |
+
output_dir: str = "detections",
|
31 |
+
frame_skip: int = 7,
|
32 |
+
conf: float = 0.75,
|
33 |
+
) -> str:
|
34 |
+
"""
|
35 |
+
Runs YOLO detection on the given video.
|
36 |
+
- Detects only class_id 0..5 (Danger / Handgun / Knife, etc.)
|
37 |
+
- Draws red bounding boxes
|
38 |
+
- Saves logs to a text file
|
39 |
+
- Saves detected frames as sequential PNG images (1.png, 2.png, 3.png, ...)
|
40 |
+
"""
|
41 |
+
|
42 |
+
if not os.path.exists(video_path):
|
43 |
+
return f"Video not found: {video_path}"
|
44 |
+
|
45 |
+
try:
|
46 |
+
model = YOLO(model_path)
|
47 |
+
except Exception as e:
|
48 |
+
return f"Failed to load model: {e}"
|
49 |
+
|
50 |
+
cap = cv2.VideoCapture(video_path)
|
51 |
+
if not cap.isOpened():
|
52 |
+
return f"Cannot open video: {video_path}"
|
53 |
+
|
54 |
+
os.makedirs(output_dir, exist_ok=True)
|
55 |
+
output_txt = os.path.join(output_dir, "detections.txt")
|
56 |
+
|
57 |
+
frame_count = 0
|
58 |
+
saved_image_index = 1 # Sıralı kaydetmek için sayaç
|
59 |
+
|
60 |
+
with open(output_txt, "w") as ftxt:
|
61 |
+
while cap.isOpened():
|
62 |
+
ret, frame = cap.read()
|
63 |
+
if not ret:
|
64 |
+
break
|
65 |
+
|
66 |
+
results = model(frame, conf=conf)
|
67 |
+
detections = (
|
68 |
+
results[0].boxes.data.cpu().numpy() if len(results) > 0 else []
|
69 |
+
)
|
70 |
+
|
71 |
+
valid_detections = [det for det in detections if int(det[5]) in [0, 1, 2, 3, 4, 5]]
|
72 |
+
|
73 |
+
if len(valid_detections) > 0:
|
74 |
+
for det in valid_detections:
|
75 |
+
x1, y1, x2, y2, conf_score, cls_ = det
|
76 |
+
class_id = int(cls_)
|
77 |
+
|
78 |
+
if class_id in [0, 1, 2]:
|
79 |
+
class_label = "Danger"
|
80 |
+
elif class_id in [3, 4, 5]:
|
81 |
+
# Alternatif class isimleri
|
82 |
+
class_label = model.names.get(class_id, f"Class {class_id}")
|
83 |
+
else:
|
84 |
+
class_label = f"Class {class_id}"
|
85 |
+
|
86 |
+
cv2.rectangle(
|
87 |
+
frame,
|
88 |
+
(int(x1), int(y1)),
|
89 |
+
(int(x2), int(y2)),
|
90 |
+
(0, 0, 255),
|
91 |
+
3,
|
92 |
+
)
|
93 |
+
|
94 |
+
(w, h), _ = cv2.getTextSize(class_label, cv2.FONT_HERSHEY_COMPLEX, 0.8, 2)
|
95 |
+
label_x1 = int(x1)
|
96 |
+
label_y2 = int(y1)
|
97 |
+
label_y1 = label_y2 - h - 10
|
98 |
+
label_x2 = label_x1 + w + 10
|
99 |
+
|
100 |
+
cv2.rectangle(
|
101 |
+
frame,
|
102 |
+
(label_x1, label_y1),
|
103 |
+
(label_x2, label_y2),
|
104 |
+
(0, 0, 255),
|
105 |
+
cv2.FILLED,
|
106 |
+
)
|
107 |
+
|
108 |
+
cv2.putText(
|
109 |
+
frame,
|
110 |
+
class_label,
|
111 |
+
(label_x1 + 5, label_y1 + h + 5),
|
112 |
+
cv2.FONT_HERSHEY_COMPLEX,
|
113 |
+
0.85,
|
114 |
+
(255, 255, 255),
|
115 |
+
2,
|
116 |
+
cv2.LINE_AA,
|
117 |
+
)
|
118 |
+
|
119 |
+
ftxt.write(
|
120 |
+
f"Frame {frame_count}: {class_label} at ({int(x1)}, {int(y1)}, {int(x2)}, {int(y2)})\n"
|
121 |
+
)
|
122 |
+
|
123 |
+
# Kaydedilecek resim ismi -> 1.png, 2.png, 3.png
|
124 |
+
output_frame_path = os.path.join(output_dir, f"{saved_image_index}.png")
|
125 |
+
cv2.imwrite(output_frame_path, frame)
|
126 |
+
saved_image_index += 1
|
127 |
+
|
128 |
+
frame_count += frame_skip
|
129 |
+
cap.set(cv2.CAP_PROP_POS_FRAMES, frame_count)
|
130 |
+
|
131 |
+
cap.release()
|
132 |
+
cv2.destroyAllWindows()
|
133 |
+
|
134 |
+
return f"Processing complete. Outputs saved in '{output_dir}' and '{output_txt}'."
|
135 |
+
|
136 |
+
@tool("video_detection_tool", return_direct=True)
|
137 |
+
def video_detection_tool(video) -> str:
|
138 |
+
"""
|
139 |
+
Handles video uploads dynamically and runs YOLO detection.
|
140 |
+
Expects that the input 'video' is either a file-like object with a 'name' attribute
|
141 |
+
or a string representing the file path.
|
142 |
+
"""
|
143 |
+
if not os.path.exists(UPLOAD_FOLDER):
|
144 |
+
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
145 |
+
|
146 |
+
# Eğer video bir dosya nesnesiyse video.name kullan, aksi halde doğrudan video string'idir.
|
147 |
+
try:
|
148 |
+
video_name = video.name
|
149 |
+
except AttributeError:
|
150 |
+
video_name = video
|
151 |
+
|
152 |
+
video_path = os.path.join(UPLOAD_FOLDER, os.path.basename(video_name))
|
153 |
+
if os.path.abspath(video_name) != os.path.abspath(video_path):
|
154 |
+
shutil.copy(video_name, video_path)
|
155 |
+
return detect_with_yolo(video_path)
|
156 |
+
|
157 |
+
if __name__ == "__main__":
|
158 |
+
print("LangChain YOLO Agent Ready!")
|
159 |
+
|
160 |
+
__all__ = ["video_detection_tool"]
|