8000 Merge pull request #143 from kkroening/tensorflow-stream-example · Powercoder64/ffmpeg-python@8d7ec92 · GitHub
[go: up one dir, main page]

Skip to content

Commit 8d7ec92

Browse files
authored
Merge pull request kkroening#143 from kkroening/tensorflow-stream-example
Add tensorflow-stream example
2 parents e0eb332 + c533687 commit 8d7ec92

File tree

5 files changed

+312
-0
lines changed

5 files changed

+312
-0
lines changed

examples/README.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,59 @@ out.run()
123123

124124
<img src="https://raw.githubusercontent.com/kkroening/ffmpeg-python/master/doc/jupyter-demo.gif" alt="jupyter demo" width="75%" />
125125

126+
## [Tensorflow Streaming](https://github.com/kkroening/ffmpeg-python/blob/master/examples/tensorflow_stream.py)
127+
128+
<img src="https://raw.githubusercontent.com/kkroening/ffmpeg-python/master/examples/graphs/tensorflow-stream.png" alt="tensorflow streaming; challenge mode: combine this with the webcam example below" width="55%" />
129+
130+
- Decode input video with ffmpeg
131+
- Process video with tensorflow using "deep dream" example
132+
- Encode output video with ffmpeg
133+
134+
```python
135+
args1 = (
136+
ffmpeg
137+
.input(in_filename)
138+
.output('pipe:', format='rawvideo', pix_fmt='rgb24', vframes=8)
139+
.compile()
140+
)
141+
process1 = subprocess.Popen(args1, stdout=subprocess.PIPE)
142+
143+
args2 = (
144+
ffmpeg
145+
.input('pipe:', format='rawvideo', pix_fmt='rgb24', s='{}x{}'.format(width, height))
146+
.output(out_filename, pix_fmt='yuv420p')
147+
.overwrite_output()
148+
.compile()
149+
)
150+
process2 = subprocess.Popen(args2, stdin=subprocess.PIPE)
151+
152+
while True:
153+
in_bytes = process1.stdout.read(width * height * 3)
154+
in_frame (
155+
np
156+
.frombuffer(in_bytes, np.uint8)
157+
.reshape([height, width, 3])
158+
)
159+
160+
# See examples/tensorflow_stream.py:
161+
frame = deep_dream.process_frame(frame)
162+
163+
process2.stdin.write(
164+
frame
165+
.astype(np.uint8)
166+
.tobytes()
167+
)
168+
```
169+
170+
<img src="https://raw.githubusercontent.com/kkroening/ffmpeg-python/master/examples/graphs/dream.png" alt="deep dream streaming" width="40%" />
171+
172+
## [FaceTime webcam input](https://github.com/kkroening/ffmpeg-python/blob/master/examples/facetime.py)
173+
174+
```python
175+
(
176+
ffmpeg
177+
.input('FaceTime', format='avfoundation', pix_fmt='uyvy422', framerate=30)
178+
.output('out.mp4', pix_fmt='yuv420p', vframes=100)
179+
.run()
180+
)
181+
```

examples/facetime.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import ffmpeg
2+
3+
(
4+
ffmpeg
5+
.input('FaceTime', format='avfoundation', pix_fmt='uyvy422', framerate=30)
6+
.output('out.mp4', pix_fmt='yuv420p', vframes=100)
7+
.run()
8+
)

examples/graphs/dream.png

700 KB
Loading

examples/graphs/tensorflow-stream.png

5.86 KB
Loading

examples/tensorflow_stream.py

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
'''Example streaming ffmpeg numpy processing.
2+
3+
Demonstrates using ffmpeg to decode video input, process the frames in
4+
python, and then encode video output using ffmpeg.
5+
6+
This example uses two ffmpeg processes - one to decode the input video
7+
and one to encode an output video - while the raw frame processing is
8+
done in python with numpy.
9+
10+
At a high level, the signal graph looks like this:
11+
12+
(input video) -> [ffmpeg process 1] -> [python] -> [ffmpeg process 2] -> (output video)
13+
14+
F438 This example reads/writes video files on the local filesystem, but the
15+
same pattern can be used for other kinds of input/output (e.g. webcam,
16+
rtmp, etc.).
17+
18+
The simplest processing example simply darkens each frame by
19+
multiplying the frame's numpy array by a constant value; see
20+
``process_frame_simple``.
21+
22+
A more sophisticated example processes each frame with tensorflow using
23+
the "deep dream" tensorflow tutorial; activate this mode by calling
24+
the script with the optional `--dream` argument. (Make sure tensorflow
25+
is installed before running)
26+
'''
27+
from __future__ import print_function
28+
import argparse
29+
import ffmpeg
30+
import logging
31+
import numpy as np
32+
import os
33+
import subprocess
34+
import zipfile
35+
36+
37+
parser = argparse.ArgumentParser(description='Example streaming ffmpeg numpy processing')
38+
parser.add_argument('in_filename', help='Input filename')
39+
parser.add_argument('out_filename', help='Output filename')
40+
parser.add_argument(
41+
'--dream', action='store_true', help='Use DeepDream frame processing (requires tensorflow)')
42+
43+
logger = logging.getLogger(__name__)
44+
logging.basicConfig(level=logging.INFO)
45+
46+
47+
def get_video_size(filename):
48+
logger.info('Getting video size for {!r}'.format(filename))
49+
probe = ffmpeg.probe(filename)
50+
video_info = next(s for s in probe['streams'] if s['codec_type'] == 'video')
51+
width = int(video_info['width'])
52+
height = int(video_info['height'])
53+
return width, height
54+
55+
56+
def start_ffmpeg_process1(in_filename):
57+
logger.info('Starting ffmpeg process1')
58+
args = (
59+
ffmpeg
60+
.input(in_filename)
61+
.output('pipe:', format='rawvideo', pix_fmt='rgb24', vframes=8)
62+
.compile()
63+
)
64+
return subprocess.Popen(args, stdout=subprocess.PIPE)
65+
66+
67+
def start_ffmpeg_process2(out_filename, width, height):
68+
logger.info('Starting ffmpeg process2')
69+
args = (
70+
ffmpeg
71+
.input('pipe:', format='rawvideo', pix_fmt='rgb24', s='{}x{}'.format(width, height))
72+
.output(out_filename, pix_fmt='yuv420p')
73+
.overwrite_output()
74+
.compile()
75+
)
76+
return subprocess.Popen(args, stdin=subprocess.PIPE)
77+
78+
79+
def read_frame(process1, width, height):
80+
logger.debug('Reading frame')
81+
82+
# Note: RGB24 == 3 bytes per pixel.
83+
frame_size = width * height * 3
84+
in_bytes = process1.stdout.read(frame_size)
85+
if len(in_bytes) == 0:
86+
frame = None
87+
else:
88+
assert len(in_bytes) == frame_size
89+
frame = (
90+
np
91+
.frombuffer(in_bytes, np.uint8)
92+
.reshape([height, width, 3])
93+
)
94+
return frame
95+
96+
97+
def process_frame_simple(frame):
98+
'''Simple processing example: darken frame.'''
99+
return frame * 0.3
100+
101+
102+
def write_frame(process2, frame):
103+
logger.debug('Writing frame')
104+
process2.stdin.write(
105+
frame
106+
.astype(np.uint8)
107+
.tobytes()
108+
)
109+
110+
111+
def run(in_filename, out_filename, process_frame):
112+
width, height = get_video_size(in_filename)
113+
process1 = start_ffmpeg_process1(in_filename)
114+
process2 = start_ffmpeg_process2(out_filename, width, height)
115+
while True:
116+
frame = read_frame(process1, width, height)
117+
if frame is None:
118+
logger.info('End of input stream')
119+
break
120+
121+
logger.debug('Processing frame')
122+
frame = process_frame(frame)
123+
write_frame(process2, frame)
124+
125+
logger.info('Waiting for ffmpeg process1')
126+
process1.wait()
127+
128+
logger.info('Waiting for ffmpeg process2')
129+
process2.stdin.close()
130+
process2.wait()
131+
132+
logger.info('Done')
133+
134+
135+
class DeepDream(object):
136+
'''DeepDream implementation, adapted from official tensorflow deepdream tutorial:
137+
https://github.com/tensorflow/tensorflow/tree/master/tensorflow/examples/tutorials/deepdream
138+
139+
Credit: Alexander Mordvintsev
140+
'''
141+
142+
_DOWNLOAD_URL = 'https://storage.googleapis.com/download.tensorflow.org/models/inception5h.zip'
143+
_ZIP_FILENAME = 'deepdream_model.zip'
144+
_MODEL_FILENAME = 'tensorflow_inception_graph.pb'
145+
146+
@staticmethod
147+
def _download_model():
148+
logger.info('Downloading deepdream model...')
149+
try:
150+
from urllib.request import urlretrieve # python 3
151+
except ImportError:
152+
from urllib import urlretrieve # python 2
153+
urlretrieve(DeepDream._DOWNLOAD_URL, DeepDream._ZIP_FILENAME)
154+
155+
logger.info('Extracting deepdream model...')
156+
zipfile.ZipFile(DeepDream._ZIP_FILENAME, 'r').extractall('.')
157+
158+
@staticmethod
159+
def _tffunc(*argtypes):
160+
'''Helper that transforms TF-graph generating function into a regular one.
161+
See `_resize` function below.
162+
'''
163+
placeholders = list(map(tf.placeholder, argtypes))
164+
def wrap(f):
165+
out = f(*placeholders)
166+
def wrapper(*args, **kw):
167+
return out.eval(dict(zip(placeholders, args)), session=kw.get('session'))
168+
return wrapper
169+
return wrap
170+
171+
@staticmethod
172+
def _base_resize(img, size):
173+
'''Helper function that uses TF to resize an image'''
174+
img = tf.expand_dims(img, 0)
175+
return tf.image.resize_bilinear(img, size)[0,:,:,:]
176+
177+
def __init__(self):
178+
if not os.path.exists(DeepDream._MODEL_FILENAME):
179+
self._download_model()
180+
181+
self._graph = tf.Graph()
182+
self._session = tf.InteractiveSession(graph=self._graph)
183+
self._resize = self._tffunc(np.float32, np.int32)(self._base_resize)
184+
with tf.gfile.FastGFile(DeepDream._MODEL_FILENAME, 'rb') as f:
185+
graph_def = tf.GraphDef()
186+
graph_def.ParseFromString(f.read())
187+
self._t_input = tf.placeholder(np.float32, name='input') # define the input tensor
188+
imagenet_mean = 117.0
189+
t_preprocessed = tf.expand_dims(self._t_input-imagenet_mean, 0)
190+
tf.import_graph_def(graph_def, {'input':t_preprocessed})
191+
192+
self.t_obj = self.T('mixed4d_3x3_bottleneck_pre_relu')[:,:,:,139]
193+
#self.t_obj = tf.square(self.T('mixed4c'))
194+
195+
def T(self, layer_name):
196+
'''Helper for getting layer output tensor'''
197+
return self._graph.get_tensor_by_name('import/%s:0'%layer_name)
198+
199+
def _calc_grad_tiled(self, img, t_grad, tile_size=512):
200+
'''Compute the value of tensor t_grad over the image in a tiled way.
201+
Random shifts are applied to the image to blur tile boundaries over
202+
multiple iterations.'''
203+
sz = tile_size
204+
h, w = img.shape[:2]
205+
sx, sy = np.random.randint(sz, size=2)
206+
img_shift = np.roll(np.roll(img, sx, 1), sy, 0)
207+
grad = np.zeros_like(img)
208+
for y in range(0, max(h-sz//2, sz),sz):
209+
for x in range(0, max(w-sz//2, sz),sz):
210+
sub = img_shift[y:y+sz,x:x+sz]
211+
g = self._session.run(t_grad, {self._t_input:sub})
212+
grad[y:y+sz,x:x+sz] = g
213+
return np.roll(np.roll(grad, -sx, 1), -sy, 0)
214+
215+
def process_frame(self, frame, iter_n=10, step=1.5, octave_n=4, octave_scale=1.4):
216+
t_score = tf.reduce_mean(self.t_obj) # defining the optimization objective
217+
t_grad = tf.gradients(t_score, self._t_input)[0] # behold the power of automatic differentiation!
218+
219+
# split the image into a number of octaves
220+
img = frame
221+
octaves = []
222+
for i in range(octave_n-1):
223+
hw = img.shape[:2]
224+
lo = self._resize(img, np.int32(np.float32(hw)/octave_scale))
225+
hi = img-self._resize(lo, hw)
226+
img = lo
227+
octaves.append(hi)
228+
229+
# generate details octave by octave
230+
for octave in range(octave_n):
231+
if octave>0:
232+
hi = octaves[-octave]
233+
img = self._resize(img, hi.shape[:2])+hi
234+
for i in range(iter_n):
235+
g = self._calc_grad_tiled(img, t_grad)
236+
img += g*(step / (np.abs(g).mean()+1e-7))
237+
#print('.',end = ' ')
238+
return img
239+
240+
241+
if __name__ == '__main__':
242+
args = parser.parse_args()
243+
if args.dream:
244+
import tensorflow as tf
245+
process_frame = DeepDream().process_frame
246+
else:
247+
process_frame = process_frame_simple
248+
run(args.in_filename, args.out_filename, process_frame)

0 commit comments

Comments
 (0)
0