Spaces:
Sleeping
Sleeping
Upload 2 files
Browse files- app.py +254 -0
- requirements.txt +7 -0
app.py
ADDED
@@ -0,0 +1,254 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from deepface import DeepFace
|
2 |
+
import pandas as pd
|
3 |
+
import numpy as np
|
4 |
+
import os
|
5 |
+
from pathlib import Path
|
6 |
+
import datetime as dt
|
7 |
+
from PIL import Image, ImageDraw, ImageFont
|
8 |
+
import matplotlib.pyplot as plt
|
9 |
+
import gradio as gr
|
10 |
+
|
11 |
+
from plotly.subplots import make_subplots
|
12 |
+
import plotly.graph_objects as go
|
13 |
+
|
14 |
+
def get_download_btn(inp_file=None, is_raw_file=True):
|
15 |
+
if is_raw_file:
|
16 |
+
label = 'Скачать полный результат в формате .csv'
|
17 |
+
else:
|
18 |
+
label = 'Скачать статистику в формате .csv'
|
19 |
+
download_btn = gr.DownloadButton(
|
20 |
+
label=label,
|
21 |
+
value=inp_file,
|
22 |
+
visible=inp_file is not None,
|
23 |
+
)
|
24 |
+
return download_btn
|
25 |
+
|
26 |
+
def print_faces(face_objs, image_path):
|
27 |
+
# открыть картинку и создать объект для рисования
|
28 |
+
pil_image = Image.open(image_path)
|
29 |
+
draw = ImageDraw.Draw(pil_image)
|
30 |
+
|
31 |
+
line_widht = int(max(pil_image.size) * 0.003)
|
32 |
+
font_size = int(max(pil_image.size) * 0.015)
|
33 |
+
|
34 |
+
# настройки отрисовки
|
35 |
+
color = 'red'
|
36 |
+
font_path = 'LiberationMono-Regular.ttf'
|
37 |
+
font = ImageFont.truetype(str(font_path), size=font_size)
|
38 |
+
big_font = ImageFont.truetype(str(font_path), size=2*font_size)
|
39 |
+
|
40 |
+
# итерация по словарям для каждого лица
|
41 |
+
for i, res_dict in enumerate(face_objs):
|
42 |
+
# извлечение артибутов
|
43 |
+
age = res_dict['age']
|
44 |
+
x, y, w, h, left_eye, right_eye = res_dict['region'].values()
|
45 |
+
gender = res_dict['dominant_gender']
|
46 |
+
race = res_dict['dominant_race']
|
47 |
+
emotion = res_dict['dominant_emotion']
|
48 |
+
text_age = f'Возраст:{age}'
|
49 |
+
text_gender = f'Пол:{gender}'
|
50 |
+
text_race = f'Раса:{race}'
|
51 |
+
text_emo = f'Эмоция:{emotion}'
|
52 |
+
|
53 |
+
# отрисовка боксов и надписей
|
54 |
+
draw.rectangle((x, y, x + w, y + h), outline=color, width=line_widht)
|
55 |
+
draw.text(xy=(x + 10, y + 2*font_size), text=str(i), font=big_font, fill=color, anchor="lb")
|
56 |
+
draw.text(xy=(x, y - font_size), text=text_gender, font=font, fill=color, anchor="lb")
|
57 |
+
draw.text(xy=(x, y), text=text_age, font=font, fill=color, anchor="lb")
|
58 |
+
draw.text(xy=(x, y + h + font_size), text=text_race, font=font, fill=color, anchor="lb")
|
59 |
+
draw.text(xy=(x, y + h + 2*font_size), text=text_emo, font=font, fill=color, anchor="lb")
|
60 |
+
|
61 |
+
return pil_image
|
62 |
+
|
63 |
+
def get_stat(images_path):
|
64 |
+
'''
|
65 |
+
Функция на вход принимает путь к файлам, а возвращает датафрейм
|
66 |
+
с результатом обработки изображений
|
67 |
+
'''
|
68 |
+
# создаем пустой список для запиcи результатов
|
69 |
+
|
70 |
+
result_lst = []
|
71 |
+
result_image = np.nan
|
72 |
+
# создаем список картинок
|
73 |
+
for image in images_path:
|
74 |
+
# получим дату из названия
|
75 |
+
datetime = image.split('/')[-1].split('.')[0]
|
76 |
+
# получим данные из изображений
|
77 |
+
try:
|
78 |
+
face_objs = DeepFace.analyze(
|
79 |
+
img_path = image,
|
80 |
+
actions = ['age', 'gender', 'race', 'emotion'],
|
81 |
+
detector_backend = 'retinaface',
|
82 |
+
silent = True
|
83 |
+
)
|
84 |
+
if pd.isna(result_image):
|
85 |
+
result_image = print_faces(face_objs, image)
|
86 |
+
|
87 |
+
except ValueError:
|
88 |
+
face_objs = [{'region':{'x': 0,
|
89 |
+
'y': 0,
|
90 |
+
'w': 0,
|
91 |
+
'h': 0,
|
92 |
+
'left_eye': 0,
|
93 |
+
'right_eye': 0},
|
94 |
+
'age': np.nan,
|
95 |
+
'dominant_gender': np.nan,
|
96 |
+
'dominant_race': np.nan,
|
97 |
+
'dominant_emotion': np.nan}]
|
98 |
+
res_face_objs = []
|
99 |
+
needed_keys = ['region', 'age', 'dominant_gender', 'dominant_race', 'dominant_emotion']
|
100 |
+
for res_dict in face_objs:
|
101 |
+
new_dict = dict((k, res_dict[k]) for k in needed_keys if k in res_dict)
|
102 |
+
new_dict['img_name'] = image.split('/')[-1]
|
103 |
+
new_dict['img_path'] = image
|
104 |
+
new_dict['datetime'] = datetime
|
105 |
+
res_face_objs.append(new_dict)
|
106 |
+
del new_dict
|
107 |
+
del face_objs
|
108 |
+
# добавим результаты в список
|
109 |
+
result_lst.extend(res_face_objs)
|
110 |
+
del res_face_objs
|
111 |
+
df = pd.DataFrame(result_lst)
|
112 |
+
|
113 |
+
df = df.reset_index()
|
114 |
+
df = df.rename(columns={
|
115 |
+
'dominant_gender': 'gender',
|
116 |
+
'dominant_race': 'race',
|
117 |
+
'dominant_emotion': 'emotion',
|
118 |
+
'index': 'id'
|
119 |
+
})
|
120 |
+
|
121 |
+
answer = f'''Проанализировано изображений: {len(images_path)}.
|
122 |
+
Найдено людей: {len(df.dropna())}'''
|
123 |
+
|
124 |
+
df['datetime'] = pd.to_datetime(df['datetime'], errors='coerce')
|
125 |
+
df['date'] = df['datetime'].dt.round('h')
|
126 |
+
df['age'] = df['age'].astype('Int32')
|
127 |
+
|
128 |
+
df.to_csv('raw_result.csv', index=False)
|
129 |
+
df[['id', 'datetime', 'date', 'age', 'gender', 'race', 'emotion']].to_csv('clean_result.csv', index=False)
|
130 |
+
|
131 |
+
|
132 |
+
|
133 |
+
#=========Графики=======
|
134 |
+
data1 = df.groupby('date')['id'].count().reset_index()
|
135 |
+
data2 = df.groupby('gender')['id'].count().reset_index()
|
136 |
+
data4 = df.groupby('emotion')['id'].count().reset_index()
|
137 |
+
data5 = df.groupby('race')['id'].count().reset_index()
|
138 |
+
fig = make_subplots(
|
139 |
+
rows=3, cols=2,
|
140 |
+
specs=[[{"colspan": 2}, None],
|
141 |
+
[{}, {}],
|
142 |
+
[{}, {}]],
|
143 |
+
subplot_titles=('Количество людей',
|
144 |
+
'Гистограмма возраста',
|
145 |
+
'Пол',
|
146 |
+
'Эмоции',
|
147 |
+
'Расы'),
|
148 |
+
shared_xaxes=False,
|
149 |
+
vertical_spacing=0.1)
|
150 |
+
# Количество людей
|
151 |
+
fig.add_trace(go.Scatter(x=data1['date'], y=data1['id'],
|
152 |
+
mode='lines+markers',
|
153 |
+
name='Количество людей',
|
154 |
+
marker_color = 'indianred'), row=1, col=1)
|
155 |
+
fig.update_xaxes(title_text = "Дата", row=1, col=1)
|
156 |
+
fig.update_yaxes(title_text = "Количество", row=1, col=1)
|
157 |
+
# Гистограмма возраста
|
158 |
+
fig.add_trace(go.Histogram(x=df.loc[df['gender'] == 'Man', 'age'],
|
159 |
+
name='Мужчины',
|
160 |
+
marker_color='lightsalmon'),row=2, col=1)
|
161 |
+
fig.add_trace(go.Histogram(x=df.loc[df['gender'] == 'Woman', 'age'],
|
162 |
+
name='Женщины',
|
163 |
+
marker_color='indianred'), row=2, col=1)
|
164 |
+
fig.update_xaxes(title_text = "Возраст", row=2, col=1)
|
165 |
+
fig.update_yaxes(title_text = "Количество", row=2, col=1)
|
166 |
+
# Пол
|
167 |
+
fig.add_trace(go.Bar(x=data2['gender'],
|
168 |
+
y=data2['id'],
|
169 |
+
text=data2['id'],
|
170 |
+
textposition='auto',
|
171 |
+
marker_color='lightsalmon'), row=2, col=2)
|
172 |
+
fig.update_xaxes(title_text = "Пол", row=2, col=2)
|
173 |
+
fig.update_yaxes(title_text = "Количество", row=2, col=2)
|
174 |
+
# Эмоции
|
175 |
+
fig.add_trace(go.Bar(x=data4['emotion'],
|
176 |
+
y=data4['id'],
|
177 |
+
text=data4['id'],
|
178 |
+
textposition='auto',
|
179 |
+
marker_color='lightsalmon'), row=3, col=1)
|
180 |
+
fig.update_xaxes(title_text = "Эмоции", row=3, col=1)
|
181 |
+
fig.update_yaxes(title_text = "Количество", row=3, col=1)
|
182 |
+
# Расы
|
183 |
+
fig.add_trace(go.Bar(x=data5['race'],
|
184 |
+
y=data5['id'],
|
185 |
+
text=data5['id'],
|
186 |
+
textposition='auto',
|
187 |
+
marker_color='lightsalmon'), row=3, col=2)
|
188 |
+
|
189 |
+
fig.update_xaxes(title_text = "Расы", row=3, col=2)
|
190 |
+
fig.update_yaxes(title_text = "Количество", row=3, col=2)
|
191 |
+
|
192 |
+
fig.update_layout(
|
193 |
+
showlegend=False,
|
194 |
+
title_text='Графики атрибутов',
|
195 |
+
barmode='stack',
|
196 |
+
autosize=False,
|
197 |
+
width=1000,
|
198 |
+
height=1200
|
199 |
+
)
|
200 |
+
|
201 |
+
return df, answer, result_image, fig
|
202 |
+
|
203 |
+
with gr.Blocks(theme=gr.themes.Citrus()) as demo:
|
204 |
+
# состояние с путем до файла
|
205 |
+
raw_result_path = gr.State('raw_result.csv')
|
206 |
+
clean_result_path = gr.State('clean_result.csv')
|
207 |
+
is_raw_file = gr.State(False)
|
208 |
+
gr.Markdown(
|
209 |
+
"""
|
210 |
+
# Определение количества людей на изображениях, их пола, возраста, расы и эмоций
|
211 |
+
Введите путь до ваших изображений и получите результат.
|
212 |
+
"""
|
213 |
+
)
|
214 |
+
with gr.Tab('Обзор'):
|
215 |
+
inp = gr.Files(file_count='directory')
|
216 |
+
btn = gr.Button("Получить результат")
|
217 |
+
res_text = gr.Textbox(label="Результаты")
|
218 |
+
with gr.Row():
|
219 |
+
with gr.Column():
|
220 |
+
res_data = gr.Dataframe()
|
221 |
+
raw_download_btn = get_download_btn(inp_file=None)
|
222 |
+
clean_download_btn = get_download_btn(inp_file=None)
|
223 |
+
res_img = gr.Image(label='Пример изображения')
|
224 |
+
|
225 |
+
with gr.Tab('Графики атрибутов'):
|
226 |
+
plot = gr.Plot()
|
227 |
+
|
228 |
+
out = [res_data, res_text, res_img, plot]
|
229 |
+
clean_dbtn_inp = [clean_result_path, is_raw_file]
|
230 |
+
btn.click(
|
231 |
+
fn=get_stat,
|
232 |
+
inputs=inp,
|
233 |
+
outputs=out,
|
234 |
+
).success(
|
235 |
+
fn=get_download_btn,
|
236 |
+
inputs=[raw_result_path],
|
237 |
+
outputs=raw_download_btn
|
238 |
+
).success(
|
239 |
+
fn=get_download_btn,
|
240 |
+
inputs=clean_dbtn_inp,
|
241 |
+
outputs=clean_download_btn
|
242 |
+
)
|
243 |
+
|
244 |
+
raw_download_btn.click(
|
245 |
+
lambda path: None,
|
246 |
+
inputs=[raw_result_path],
|
247 |
+
outputs=None
|
248 |
+
)
|
249 |
+
clean_download_btn.click(
|
250 |
+
lambda path: None,
|
251 |
+
inputs=[clean_result_path],
|
252 |
+
outputs=None
|
253 |
+
)
|
254 |
+
demo.launch()
|
requirements.txt
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
pandas==2.2.2
|
2 |
+
numpy==1.26.4
|
3 |
+
deepface==0.0.93
|
4 |
+
gradio==5.4.0
|
5 |
+
plotly==5.24.1
|
6 |
+
matplotlib==3.7.1
|
7 |
+
pillow==10.4.0
|