-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstream_video.py
181 lines (132 loc) · 5.31 KB
/
stream_video.py
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
import socket
from flask import Flask, Response
from PIL import Image, ImageDraw
import threading
from collections import deque
import struct
import io
HOST = '0.0.0.0'
PORT = 54321
image_bytes_length = 640*480*3
bbox_bytes_length = 5*8
# The socket client sends one bounding box and score.
data_bytes_length = image_bytes_length + bbox_bytes_length
app = Flask(__name__)
# The buffer that holds the most recent JPEG frame.
stream_buffer = deque(maxlen=1)
# global flag
should_capture = False
# The buffer that holds the most recent captured JPEG frame, which is either the first frame after the should_capture flag is set, or the latest frame showing a missing tooth.
capture_buffer = deque(maxlen=1)
# This functions returns an additional flag indicating whether a missing tooth bounding box has been drawn.
def to_jpeg(image_bytes, bbox_bytes):
# Unpack the bytes to a list of floats.
f = []
for i in range(5):
# Each float was encoded into 8 bytes.
float_bytes = bbox_bytes[8*i:8*(i+1)]
float_value, = struct.unpack('!d', float_bytes)
f.append(float_value)
# This buffer holds the JPEG image which will be a single frame of the streaming video.
bytes_buffer = io.BytesIO()
image = Image.frombytes('RGB', (640, 480), image_bytes, 'raw', 'RGB')
# Draw a box showing the part of the image that was sent to the model, with corner coordinates (0, 0) and (224, 224).
x1, y1, x2, y2 = (0.0, 0.0, 224.0, 224.0)
# These offsets invert the cropping in recognize.py:image_bytes_to_image.
x1 += 258
x2 += 258
y1 += 148
y2 += 148
draw = ImageDraw.Draw(image)
draw.line(xy=[(x1, y1), (x2, y1), (x2, y2), (x1, y2), (x1, y1)], fill=128, width=5)
del draw
# Draw an additional bounding box if a missing tooth was detected.
x1, y1, x2, y2, score = f
bbox_drawn = False
if score > 0.5:
bbox_drawn = True
# The coordinates from the DetectionEngine were normalized. Transform to the pixel scale before drawing.
x1 *= 224
x2 *= 224
y1 *= 224
y2 *= 224
# Place the cropped (224, 224) image back in the (640, 480) image at the corret position.
x1 += 258
x2 += 258
y1 += 148
y2 += 148
draw = ImageDraw.Draw(image)
draw.line(xy=[(x1, y1), (x2, y1), (x2, y2), (x1, y2), (x1, y1)], fill=128, width=5)
# Write image to the buffer and return the JPEG bytes.
image.save(bytes_buffer, format='JPEG')
frame = bytes_buffer.getvalue()
return frame, bbox_drawn
def server_worker(host, port, stream_buffer, capture_buffer):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind((host, port))
s.listen()
print('Waiting for connection.')
conn, addr = s.accept()
with conn:
print('Client: {}'.format(addr))
while True:
try:
# image bytes and bounding box score bytes
data = conn.recv(data_bytes_length)
if data and len(data) == data_bytes_length:
image_bytes = data[:image_bytes_length]
bbox_bytes = data[image_bytes_length:]
frame, bbox_drawn = to_jpeg(image_bytes, bbox_bytes)
stream_buffer.append(frame)
# update the frame in capture_buffer if:
# (a) should_capture is True and capture_buffer is empty; or
# (b) should_capture is True and bbox_drawn is True
should_update = should_capture and (bbox_drawn or not capture_buffer)
if should_update:
capture_buffer.append(frame)
except Exception as e:
print(repr(e))
break
def make_generator(buffer_):
while True:
if buffer_:
# peek instead of pop, since the buffer may not always be updated
frame = buffer_[-1]
else:
continue
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
@app.route('/video')
def video():
generator = make_generator(stream_buffer)
return Response(generator,
mimetype='multipart/x-mixed-replace; boundary=frame')
@app.route('/capture')
def capture():
generator = make_generator(capture_buffer)
return Response(generator,
mimetype='multipart/x-mixed-replace; boundary=frame')
@app.route('/start_capture')
def start_capture():
# We're clearing the capture buffer here because the logic while capturing
# is to only grab a buffer frame if the buffer is empty or missing teeth
# is detected, and we want to be sure to grab a fresh still each time even
# when no teeth are detected
global capture_buffer
capture_buffer.clear()
global should_capture
should_capture = True
return 'OK', 200
@app.route('/stop_capture')
def stop_capture():
global should_capture
should_capture = False
return 'OK', 200
@app.route('/')
def index():
return 'OK', 200
if __name__ == '__main__':
thread = threading.Thread(target=server_worker, args=(HOST, PORT, stream_buffer, capture_buffer))
thread.start()
app.run(host='0.0.0.0', debug=False)
thread.join()