Duckkyy commited on
Commit
fb8409d
·
verified ·
1 Parent(s): 38217f1

Upload app.py

Browse files

Add app.py file

Files changed (1) hide show
  1. app.py +330 -0
app.py ADDED
@@ -0,0 +1,330 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import base64
2
+ import random
3
+ import shutil
4
+ import time
5
+ from openai import OpenAI
6
+ import glob
7
+ import numpy as np
8
+ import matplotlib.pyplot as plt
9
+ from sklearn import svm
10
+ import zipfile
11
+ from PIL import Image
12
+ from sklearn.decomposition import PCA
13
+ from PIL import Image
14
+ import numpy as np
15
+ from sklearn.preprocessing import StandardScaler
16
+ from sklearn.svm import OneClassSVM
17
+ import numpy as np
18
+ import skimage
19
+ from skimage.feature import hog
20
+ from skimage.color import rgb2gray
21
+ from skimage import io
22
+ from sklearn.decomposition import PCA
23
+ from sklearn.svm import OneClassSVM
24
+ from sklearn.preprocessing import StandardScaler
25
+ import os
26
+ from tqdm import tqdm
27
+ import pickle
28
+ import joblib
29
+ import cv2
30
+ import streamlit as st
31
+ from streamlit_image_select import image_select
32
+
33
+ def cut_video(video_path):
34
+ # video_path = '/Users/ducky/Downloads/thief_1.mp4'
35
+ cap = cv2.VideoCapture(video_path)
36
+ fps = cap.get(cv2.CAP_PROP_FPS)
37
+ frames_dir = "./data/video_frame"
38
+ if os.path.exists(frames_dir):
39
+ shutil.rmtree(frames_dir)
40
+ os.makedirs(frames_dir, exist_ok=True)
41
+ frame_count = 0
42
+ frame_times = []
43
+ while cap.isOpened():
44
+ ret, frame = cap.read()
45
+ if not ret:
46
+ break
47
+
48
+ timestamp_ms = (frame_count / fps) * 1000
49
+ minutes = int(timestamp_ms // 60000)
50
+ seconds = int((timestamp_ms % 60000) // 1000)
51
+ milliseconds = int(timestamp_ms % 1000)
52
+ time_formatted = f"{minutes:02}:{seconds:02}:{milliseconds:03}"
53
+ frame_times.append(time_formatted)
54
+
55
+ frame_file_path = os.path.join(frames_dir, f'frame_{frame_count:04d}.jpg')
56
+ cv2.imwrite(frame_file_path, frame)
57
+ frame_count += 1
58
+
59
+ cap.release()
60
+ return frames_dir, frame_times
61
+
62
+ def extract_hog_features(image_path):
63
+ """
64
+ 画像ファイルからHOG特徴量を抽出します。
65
+
66
+ :param image_path: 画像ファイルのパス
67
+ :return: HOG特徴量のNumPy配列
68
+ """
69
+ # 画像を読み込む
70
+ img = io.imread(image_path)
71
+ img = img[:,:,:3]
72
+
73
+ # 画像をグレースケールに変換
74
+ gray_img = rgb2gray(img)
75
+
76
+ # HOG特徴量を抽出
77
+ features, _ = hog(gray_img, visualize=True, block_norm='L2-Hys')
78
+
79
+ return features
80
+
81
+ def prepare_features(image_paths):
82
+ """
83
+ 複数の画像からHOG特徴量を抽出し、特徴量の行列を作成します。
84
+
85
+ :param image_paths: 画像ファイルのパスのリスト
86
+ :return: 特徴量のNumPy配列
87
+ """
88
+ progress_bar = st.progress(0)
89
+ status_text = st.empty()
90
+ features = []
91
+ for i, path in enumerate(tqdm(image_paths)):
92
+ features.append(extract_hog_features(path))
93
+
94
+ progress = int((i + 1) / len(image_paths) * 100)
95
+ progress_bar.progress(progress)
96
+ status_text.text(f"Processing image {i+1}/{len(image_paths)}: {path}")
97
+
98
+ progress_bar.empty()
99
+ status_text.text("Processing complete!")
100
+ status_text.empty()
101
+
102
+ return np.array(features)
103
+
104
+ def run_pca(features):
105
+ pca = PCA(n_components=4)
106
+ progress_bar = st.progress(0)
107
+ status_text = st.empty()
108
+
109
+ def simulate_pca_progress(progress_bar, status_text, total_steps=100):
110
+ for step in range(total_steps):
111
+ progress_bar.progress(int((step + 1) / total_steps * 100))
112
+ status_text.text(f"PCA Transformation Progress: {int((step + 1) / total_steps * 100)}%")
113
+ time.sleep(0.1)
114
+
115
+ simulate_pca_progress(progress_bar, status_text)
116
+ transformed_data = pca.fit_transform(features)
117
+ status_text.text("PCA Transformation Complete!")
118
+ progress_bar.empty()
119
+ status_text.empty()
120
+
121
+ return pca, transformed_data
122
+
123
+ def run_standard_scale(features):
124
+ scaler = StandardScaler()
125
+ progress_bar = st.progress(0)
126
+ status_text = st.empty()
127
+
128
+ def simulate_ss_progress(progress_bar, status_text, total_steps=100):
129
+ for step in range(total_steps):
130
+ progress_bar.progress(int((step + 1) / total_steps * 100))
131
+ status_text.text(f"StandardScaler Transformation Progress: {int((step + 1) / total_steps * 100)}%")
132
+ time.sleep(0.1)
133
+
134
+ simulate_ss_progress(progress_bar, status_text)
135
+ transformed_data = scaler.fit_transform(features)
136
+ status_text.text("StandardScaler Transformation Complete!")
137
+ progress_bar.empty()
138
+ status_text.empty()
139
+
140
+ return scaler, transformed_data
141
+
142
+ def run_OneClassSVM(z_train):
143
+ progress_bar = st.progress(0)
144
+ status_text = st.empty()
145
+
146
+ clf = svm.OneClassSVM(nu=0.2, kernel="rbf", gamma=0.001)
147
+
148
+ def simulate_fitting_progress(clf, z_train, total_steps=100):
149
+ for step in range(total_steps):
150
+ time.sleep(0.05)
151
+
152
+ progress_bar.progress(step + 1)
153
+ status_text.text(f"Fitting model... {step + 1}% complete")
154
+
155
+ clf.fit(z_train)
156
+
157
+ progress_bar.empty()
158
+ status_text.empty()
159
+
160
+ simulate_fitting_progress(clf, z_train)
161
+ return clf
162
+
163
+ def predict_with_progress(clf, features_array):
164
+ progress_bar = st.progress(0)
165
+ status_text = st.empty()
166
+
167
+ predictions = np.zeros(features_array.shape[0])
168
+
169
+ for i in range(features_array.shape[0]):
170
+ predictions[i] = clf.predict(features_array[i].reshape(1, -1))
171
+ # predictions[i] = clf.decision_function(features_array[i].reshape(1, -1))
172
+ progress = int((i + 1) / features_array.shape[0] * 100)
173
+ progress_bar.progress(progress)
174
+ status_text.text(f"Predicting... {progress}% complete")
175
+
176
+ progress_bar.empty()
177
+ status_text.empty()
178
+
179
+ return predictions
180
+
181
+ def prepare_all_displayed_anomalies(frames_dir, predictions):
182
+ anomaly_indices = [index for index, value in enumerate(predictions) if value == -1]
183
+ anomaly_indices.sort()
184
+
185
+ frames = os.listdir(frames_dir)
186
+ frames.sort()
187
+
188
+ anomaly_folder = "./data/anomaly"
189
+ os.makedirs(anomaly_folder, exist_ok=True)
190
+ anomaly_paths = []
191
+ frame_number = 0
192
+ anomaly_count = 0
193
+ for frame in frames:
194
+ frame_path = os.path.join(frames_dir, frame)
195
+ if frame_number == anomaly_indices[anomaly_count]:
196
+ anomaly_frame_path = os.path.join(anomaly_folder, f'frame_{frame_number:04d}.jpg')
197
+ shutil.copy(frame_path, anomaly_frame_path)
198
+
199
+ anomaly_paths.append(anomaly_frame_path)
200
+ anomaly_count += 1
201
+ if anomaly_count >= len(anomaly_indices): break
202
+
203
+ frame_number += 1
204
+
205
+ return anomaly_paths
206
+
207
+ def prepare_3_displayed_anomalies(frames_dir, predictions, frame_times):
208
+ anomaly_frames = [index for index, value in enumerate(predictions) if value == -1]
209
+ indices = random.sample(range(len(anomaly_frames)), 3)
210
+ anomaly_frames = [anomaly_frames[i] for i in indices]
211
+ anomaly_frames.sort()
212
+
213
+ frames = os.listdir(frames_dir)
214
+ frames.sort()
215
+
216
+ anomaly_folder = "./data/anomaly"
217
+ os.makedirs(anomaly_folder, exist_ok=True)
218
+ anomaly_paths = []
219
+ frame_number = 0
220
+ anomaly_count = 0
221
+ for frame in frames:
222
+ frame_path = os.path.join(frames_dir, frame)
223
+ if frame_number == anomaly_frames[anomaly_count]:
224
+ anomaly_frame_path = os.path.join(anomaly_folder, f'frame_{frame_number:04d}.jpg')
225
+ shutil.copy(frame_path, anomaly_frame_path)
226
+
227
+ anomaly_paths.append([anomaly_frame_path, frame_times[frame_number]])
228
+ anomaly_count += 1
229
+ if anomaly_count >= len(anomaly_frames): break
230
+
231
+ frame_number += 1
232
+
233
+ return anomaly_paths
234
+
235
+ def OneClassSvm_anomaly_detection(image_paths):
236
+ features = prepare_features(image_paths)
237
+ _, features_scaled = run_standard_scale(features)
238
+ _, z_train = run_pca(features_scaled)
239
+ clf = run_OneClassSVM(z_train)
240
+ return clf, z_train
241
+
242
+ def encode_image(image_path):
243
+ with open(image_path, "rb") as image_file:
244
+ return base64.b64encode(image_file.read()).decode("utf-8")
245
+
246
+ def get_response(model, client, image_path):
247
+ base64_image = encode_image(image_path)
248
+ response = client.chat.completions.create(
249
+ model=model,
250
+ messages=[
251
+ {"role": "system", "content": "You are a helpful assistant that responds in Markdown. Help me with task!"},
252
+ {"role": "user", "content": [
253
+ {"type": "text", "text": "この画像の中の人物が行っている活動を説明してください"},
254
+ {"type": "image_url", "image_url": {
255
+ "url": f"data:image/png;base64,{base64_image}"}
256
+ }
257
+ ]}
258
+ ],
259
+ temperature=0.0,
260
+ )
261
+ return response.choices[0].message.content
262
+
263
+ def VLM_anomaly_detection(anomaly_paths):
264
+ model = "gpt-4o"
265
+ API_KEY = os.getenv("MY_API_KEY")
266
+ client = OpenAI(api_key=API_KEY)
267
+
268
+ progress_bar = st.progress(0)
269
+ status_text = st.empty()
270
+ st.session_state.responses = []
271
+
272
+ anomaly_paths = [path[0] for path in anomaly_paths]
273
+
274
+ for i, anomaly_path in enumerate(tqdm(anomaly_paths)):
275
+ progress = int((i + 1) / len(anomaly_paths) * 100)
276
+ progress_bar.progress(progress)
277
+ status_text.text(f"Running VLM {i+1}/{len(anomaly_paths)}")
278
+ response = get_response(model, client, anomaly_path)
279
+ st.session_state.responses.append(response)
280
+
281
+ progress_bar.empty()
282
+ status_text.text("Processing complete!")
283
+ status_text.empty()
284
+
285
+
286
+ def main():
287
+
288
+ if 'responses' not in st.session_state:
289
+ st.session_state.responses = []
290
+ if 'display_anomalies' not in st.session_state:
291
+ st.session_state.display_anomalies = []
292
+
293
+ with st.sidebar:
294
+ st.image("logo.png")
295
+ uploaded_video = st.file_uploader("Upload video", type=["mp4", "mov", "avi"])
296
+ if uploaded_video is not None:
297
+ video_file_path = "./data/uploaded_video.mp4"
298
+ with open(video_file_path, "wb") as f:
299
+ f.write(uploaded_video.read())
300
+ st.video(uploaded_video, start_time=0)
301
+
302
+ st.write("サイ��バーより動画としてアップロードし推論ボタンをクリック")
303
+ if st.button("推論開始"):
304
+ with st.spinner("データを学習中、少々お待ちください..."):
305
+ video_file_path = "./data/uploaded_video.mp4"
306
+ frames_dir, frame_times = cut_video(video_file_path)
307
+ image_paths = [os.path.join(frames_dir, image_path) for image_path in os.listdir(frames_dir)]
308
+ clf, z_train = OneClassSvm_anomaly_detection(image_paths)
309
+
310
+ with st.spinner("学習が完了しました。異常検知を行っています..."):
311
+ predictions = predict_with_progress(clf, z_train)
312
+ st.session_state.display_anomalies = []
313
+ st.session_state.display_anomalies = prepare_3_displayed_anomalies(frames_dir, predictions, frame_times)
314
+ VLM_anomaly_detection(st.session_state.display_anomalies)
315
+
316
+ if st.session_state.display_anomalies:
317
+ anomaly_paths = [path[0] for path in st.session_state.display_anomalies]
318
+ anomaly_time = [str(path[1]) for path in st.session_state.display_anomalies]
319
+ selected = image_select(
320
+ label = "「異常」である可能性があるフレーム",
321
+ images = anomaly_paths,
322
+ captions = anomaly_time,
323
+ key = "image_select"
324
+ )
325
+ selected_img = str(selected)[:100]
326
+ idx = anomaly_paths.index(selected_img)
327
+ st.info(st.session_state.responses[idx])
328
+
329
+ if __name__ == "__main__":
330
+ main()