Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

# -*- coding: utf-8 -*- 

""" 

@file 

@brief Fonctions proposant de traiter des vidéos 

avec des traitements compliqués type 

:epkg:`deep learning`. 

""" 

import os 

from moviepy.video.io.ImageSequenceClip import ImageSequenceClip 

from ..ai import DLImageSegmentation 

from .video import video_enumerate_frames 

from .moviepy_context import VideoContext 

 

 

def video_map_images(video_or_file, name, fLOG=None, **kwargs): 

""" 

Applies one complex process to a video such as 

extracting characters from videos and 

removing the backaground. It is done image by image. 

Applique un traitement compliqué sur une séquence 

d'images telle que la séparation des personnages et du fond. 

 

@param video_or_file string or :epkg:`VideoClip` 

@param name name of the processing to do, 

see the list below 

@param fLOG logging function 

@param kwargs additional parameters 

@return :epkg:`VideoClip` 

 

List of available treatments: 

 

* ``'people'``: extracts characters from a movie. 

The movie is composed with an image and a 

`mask <https://zulko.github.io/moviepy/ref/AudioClip.html?highlight=mask#moviepy.audio.AudioClip.AudioClip.set_ismask>`_. 

Parameters: see @fn video_map_images_people. 

* ``'detect'`` : blurs or put a rectangle around faces, uses :epkg:`opencv` 

 

.. warning:: A couple of errors timeout, out of memory... 

The following processes might be quite time consuming 

or memory consuming. If it is the case, you should think 

of reducing the resolution, the number of frames per seconds 

(*fps*). You can also split the video and process each piece 

independently and finally concatenate them. 

""" 

allowed = {'people'} 

if name == 'people': 

return video_map_images_people(video_or_file, fLOG=fLOG, **kwargs) 

elif name == "detect": 

return video_map_images_detect(video_or_file, fLOG=fLOG, **kwargs) 

else: 

raise ValueError("Unknown process '{}', should be among: {}".format( 

name, ','.join(allowed))) 

 

 

def video_map_images_people(video_or_file, resize=('max2', 400), fps=None, 

with_times=False, progress_bar=False, dtype=None, 

class_to_keep=15, fLOG=None, **kwargs): 

""" 

Extracts characters from a movie. 

The movie is composed with an image and a 

`mask <https://zulko.github.io/moviepy/ref/AudioClip.html?highlight=mask#moviepy.audio.AudioClip.AudioClip.set_ismask>`_. 

Extrait les personnages d'un film, le résultat est 

composé d'une image et d'un masque transparent 

qui laissera apparaître l'image d'en dessous si cette 

vidéo est aposée sur une autre. 

 

@param video_or_file string or :epkg:`VideoClip` 

@param resize see :meth:`predict <code_beatrix.ai.image_segmentation.DLImageSegmentation.predict>` 

@param fps see @see fn video_enumerate_frames 

@param with_times see @see fn video_enumerate_frames 

@param progress_bar see @see fn video_enumerate_frames 

@param dtype see @see fn video_enumerate_frames 

@param class_to_keep class to keep from the image, it can 

a number (15 for the background, a list of classes, 

a function which takes an image and the prediction 

and returns an image) 

@param fLOG logging function 

@param kwargs see @see cl DLImageSegmentation 

@return :epkg:`VideoClip` 

 

.. warning:: A couple of errors timeout, out of memory... 

The following processes might be quite time consuming 

or memory consuming. If it is the case, you should think 

of reducing the resolution, the number of frames per seconds 

(*fps*). You can also split the video and process each piece 

independently and finally concatenate them. 

 

.. exref:: 

:title: Extract characters from a video. 

 

The following example shows how to extract a movie with 

people and without the background. It works better 

if the contrast between the characters and the background is 

high. 

 

:: 

 

from code_beatrix.art.video import video_extract_video, video_save 

from code_beatrix.art.videodl import video_map_images 

 

vide = video_extract_video("something.mp4", 0, 5) 

vid2 = video_map_images(vide, fps=10, name="people", progress_bar=True) 

video_save(vid2, "people.mp4") 

 

The function returns something like the the following. 

The character is wearing black and the background is quite 

dark too. That explains that the kind of large halo 

around the character. 

 

.. video:: videodl.mp4 

""" 

if isinstance(class_to_keep, int): 

def local_mask(img, pred): 

img[pred != class_to_keep] = 0 

return img 

elif isinstance(class_to_keep, (set, tuple, list)): 

def local_mask(img, pred): 

dist = set(pred.ravel()) 

rem = set(class_to_keep) 

for cl in dist: 

if cl not in rem: 

img[pred == cl] = 0 

return img 

elif callable(class_to_keep): 

local_mask = class_to_keep 

else: 

raise TypeError("class_to_keep should be an int, a list or a function not {0}".format( 

type(class_to_keep))) 

 

if fLOG: 

fLOG('[video_map_images_people] loads deep learning model') 

model = DLImageSegmentation(fLOG=fLOG, **kwargs) 

iter = video_enumerate_frames(video_or_file, fps=fps, with_times=with_times, 

progress_bar=progress_bar, dtype=dtype, clean=False) 

if fLOG is not None: 

if fps is not None: 

every = max(fps, 1) 

unit = 's' 

else: 

every = 20 

unit = 'i' 

 

if fLOG: 

fLOG('[video_map_images_people] starts extracting characters') 

seq = [] 

for i, img in enumerate(iter): 

if not progress_bar and fLOG is not None and i % every == 0: 

fLOG('[video_map_images_people] process %d%s images' % (i, unit)) 

if resize is not None and isinstance(resize[0], str): 

if len(img.shape) == 2: 

resize = DLImageSegmentation._new_size(img.shape, resize) 

else: 

resize = DLImageSegmentation._new_size(img.shape[:2], resize) 

img, pred = model.predict(img, resize=resize) 

img2 = local_mask(img, pred) 

seq.append(img2) 

if fLOG: 

fLOG('[video_map_images_people] done.') 

 

return ImageSequenceClip(seq, fps=fps) 

 

 

def video_map_images_detect(video_or_file, fps=None, with_times=False, progress_bar=False, dtype=None, 

scaleFactor=1.3, minNeighbors=4, minSize=(30, 30), 

action='blur', color=(255, 255, 0), haar=None, fLOG=None): 

""" 

Blurs people faces. 

Uses function `detectmultiscale <https://docs.opencv.org/2.4/modules/objdetect/ 

doc/cascade_classification.html#cascadeclassifier-detectmultiscale>`_. 

Relies on :epkg:`opencv`. 

Floute les visages. 

 

@param video_or_file string or :epkg:`VideoClip` 

@param fps see @see fn video_enumerate_frames, 

faces are detected in each frame returned by 

@see fn video_enumerate_frames 

@param with_times see @see fn video_enumerate_frames 

@param progress_bar see @see fn video_enumerate_frames 

@param dtype see @see fn video_enumerate_frames 

@param fLOG logging function 

@param scaleFactor see `detectmultiscale <https://docs.opencv.org/2.4/modules/objdetect/doc/ 

cascade_classification.html#cascadeclassifier-detectmultiscale>`_ 

@param minNeighbors see `detectmultiscale <https://docs.opencv.org/2.4/modules/objdetect/doc/ 

cascade_classification.html#cascadeclassifier-detectmultiscale>`_ 

@param minSize see `detectmultiscale <https://docs.opencv.org/2.4/modules/objdetect/doc/ 

cascade_classification.html#cascadeclassifier-detectmultiscale>`_ 

@param haar shape classifier to load, face by default, see below 

@param action to blur, to put a rectangle around the detected zone... see below 

@param color rectangle color if *action* is ``'rect'`` 

@return :epkg:`VideoClip` 

 

Only ``haarcascade_frontalface_alt.xml`` is provided but you can 

get more at `haarcascades <https://github.com/opencv/opencv/blob/master/data/haarcascades/>`_. 

 

Parameter *action* can be: 

 

* ``'blur'``: to blur faces (or detector zones) 

* ``'rect'``: to draw a rectangle around faces (or detector zones) 

 

.. exref:: 

:title: Faces in a yellow box in a video 

 

The following example uses :epkg:`opencv` to detect faces 

on each image of a video and put a yellow box around each of them. 

 

:: 

 

from code_beatrix.art.videodl import video_map_images 

from code_beatrix.art.video import video_save, video_extract_video 

 

vide = video_extract_video(vid, 0, 5 if __name__ == "__main__" else 1) 

vid2 = video_map_images( 

vide, fps=10, name='detect', action='rect', 

progress_bar=True, fLOG=fLOG) 

exp = os.path.join(temp, "people.mp4") 

video_save(vid2, exp, fps=10) 

 

The following video is taken from 

`Charlie Chaplin's movies <source: https://www.youtube.com/watch?v=n_1apYo6-Ow>`_. 

 

.. video:: face.mp4 

""" 

from cv2 import CascadeClassifier, CASCADE_SCALE_IMAGE 

from .video_drawing import blur, rectangle 

 

def fl_blur(gf, t, rects): 

im = gf(t) 

ti = min(int(t * fps), len(rects) - 1) 

rects = all_rects[ti] 

for rect in rects: 

x1, y1, dx, dy = rect 

blur(im, (x1, y1), (x1 + dx, y1 + dy)) 

return im 

 

def fl_rect(gf, t, rects): 

im = gf(t) 

ti = min(int(t * fps), len(rects) - 1) 

rects = all_rects[ti] 

for rect in rects: 

x1, y1, dx, dy = rect 

rectangle(im, (x1, y1), (x1 + dx, y1 + dy), color) 

return im 

 

fcts = dict(blur=fl_blur, rect=fl_rect) 

 

if action not in fcts: 

raise ValueError("action='{0}' should be in {1}".format( 

action, list(sorted(fcts.keys())))) 

 

if fLOG: 

fLOG('[video_map_images_blur] detect faces') 

 

if haar is None: 

this = os.path.abspath(os.path.dirname(__file__)) 

cascade_fn = os.path.join( 

this, 'data', 'haarcascade_frontalface_alt.xml') 

elif not os.path.exists(haar): 

raise FileNotFoundError(haar) 

else: 

cascade_fn = haar 

 

cascade = CascadeClassifier(cascade_fn) 

 

iter = video_enumerate_frames(video_or_file, fps=fps, with_times=with_times, 

progress_bar=progress_bar, dtype=dtype, clean=False) 

 

if fLOG: 

fLOG("[video_map_images_people] starts detecting and burring faces with: {0}".format( 

cascade_fn)) 

if fps is not None: 

every = max(fps, 1) 

unit = 's' 

else: 

every = 20 

unit = 'i' 

 

all_rects = [] 

for i, img in enumerate(iter): 

if not progress_bar and fLOG is not None and i % every == 0: 

fLOG('[video_map_images_face] process %d%s images' % (i, unit)) 

 

try: 

rects = cascade.detectMultiScale(img, scaleFactor=1.3, 

minNeighbors=minNeighbors, minSize=minSize, 

flags=CASCADE_SCALE_IMAGE) 

except Exception as e: 

if fLOG: 

fLOG('Unable to retrieve any shape due to ', e) 

rects = [] 

all_rects.append(rects) 

 

if fLOG: 

non = sum(map(len, (filter(lambda x: len(x) > 0, all_rects)))) 

fLOG('[video_map_images_blur] creates video nb image: {1}, nb faces: {0}'.format( 

non, len(all_rects))) 

 

with VideoContext(video_or_file) as video: 

return video.video.fl(lambda im, t: fcts[action](im, t, all_rects), keep_duration=True)