做者|Rahul Varma
编译|VK
来源|Towards Data Sciencepython
训练和测试一个有效的机器学习模型最重要的一步是收集大量数据并使用这些数据对其进行有效训练。小批量(Mini-batches)有助于解决这个问题,在每次迭代中使用一小部分数据进行训练。git
可是,随着大量的机器学习任务在视频数据集上执行,存在着对不等长视频进行有效批处理的问题。大多数方法依赖于将视频裁剪成相等的长度,以便在迭代期间提取相同数量的帧。但在咱们须要从每一帧获取信息来有效地预测某些事情的场景中,这并非特别有用,特别是在自动驾驶汽车和动做识别的状况下。github
咱们能够建立一个能够处理不一样长度视频的处理方法。app
在Glenn Jocher的Yolov3中(https://github.com/ultralytic...,我用LoadStreams做为基础,建立了LoadStreamsBatch类。机器学习
def __init__(self, sources='streams.txt', img_size=416, batch_size=2, subdir_search=False): self.mode = 'images' self.img_size = img_size self.def_img_size = None videos = [] if os.path.isdir(sources): if subdir_search: for subdir, dirs, files in os.walk(sources): for file in files: if 'video' in magic.from_file(subdir + os.sep + file, mime=True): videos.append(subdir + os.sep + file) else: for elements in os.listdir(sources): if not os.path.isdir(elements) and 'video' in magic.from_file(sources + os.sep + elements, mime=True): videos.append(sources + os.sep + elements) else: with open(sources, 'r') as f: videos = [x.strip() for x in f.read().splitlines() if len(x.strip())] n = len(videos) curr_batch = 0 self.data = [None] * batch_size self.cap = [None] * batch_size self.sources = videos self.n = n self.cur_pos = 0 # 启动线程从视频流中读取帧 for i, s in enumerate(videos): if curr_batch == batch_size: break print('%g/%g: %s... ' % (self.cur_pos+1, n, s), end='') self.cap[curr_batch] = cv2.VideoCapture(s) try: assert self.cap[curr_batch].isOpened() except AssertionError: print('Failed to open %s' % s) self.cur_pos+=1 continue w = int(self.cap[curr_batch].get(cv2.CAP_PROP_FRAME_WIDTH)) h = int(self.cap[curr_batch].get(cv2.CAP_PROP_FRAME_HEIGHT)) fps = self.cap[curr_batch].get(cv2.CAP_PROP_FPS) % 100 frames = int(self.cap[curr_batch].get(cv2.CAP_PROP_FRAME_COUNT)) _, self.data[i] = self.cap[curr_batch].read() # guarantee first frame thread = Thread(target=self.update, args=([i, self.cap[curr_batch], self.cur_pos+1]), daemon=True) print(' success (%gx%g at %.2f FPS having %g frames).' % (w, h, fps, frames)) curr_batch+=1 self.cur_pos+=1 thread.start() print('') # 新的一行 if all( v is None for v in self.data ): return # 检查常见形状 s = np.stack([letterbox(x, new_shape=self.img_size)[0].shape for x in self.data], 0) # 推理的形状 self.rect = np.unique(s, axis=0).shape[0] == 1 if not self.rect: print('WARNING: Different stream shapes detected. For optimal performance supply similarly-shaped streams.')
在__init__函数中,接受四个参数。虽然img_size与原始版本相同,但其余三个参数定义以下:ide
我首先检查sources参数是目录仍是文本文件。若是是一个目录,我会读取目录中的全部内容(若是subdir_search参数为True,子目录也会包括在内),不然我会读取文本文件中视频的路径。视频的路径存储在列表中。使用cur_pos以跟踪列表中的当前位置。函数
该列表以batch_size为最大值进行迭代,并检查以跳过错误视频或不存在的视频。它们被发送到letterbox函数,以调整图像大小。这与原始版本相比没有任何变化,除非全部视频都有故障/不可用。学习
def letterbox(img, new_shape=(416, 416), color=(114, 114, 114), auto=True, scaleFill=False, scaleup=True): # 将图像调整为32个像素倍数的矩形 https://github.com/ultralytics/yolov3/issues/232 shape = img.shape[:2] # 当前形状 [height, width] if isinstance(new_shape, int): new_shape = (new_shape, new_shape) # 比例 r = min(new_shape[0] / shape[0], new_shape[1] / shape[1]) if not scaleup: # 只按比例缩小,不按比例放大(用于更好的测试图) r = min(r, 1.0) # 计算填充 ratio = r, r # 宽高比 new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r)) dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] #填充 if auto: # 最小矩形 dw, dh = np.mod(dw, 64), np.mod(dh, 64) # 填充 elif scaleFill: # 伸展 dw, dh = 0.0, 0.0 new_unpad = new_shape ratio = new_shape[0] / shape[1], new_shape[1] / shape[0] # 宽高比 dw /= 2 # 将填充分红两侧 dh /= 2 if shape[::-1] != new_unpad: # 改变大小 img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR) top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1)) left, right = int(round(dw - 0.1)), int(round(dw + 0.1)) img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color) # 添加边界 return img, ratio, (dw, dh)
update函数有一个小的变化,咱们另外存储了默认的图像大小,以便在全部视频都被提取进行处理,但因为长度不相等,一个视频比另外一个视频提早完成。当我解释代码的下一部分时,它会更清楚,那就是__next__ 函数。测试
def update(self, index, cap, cur_pos): # 读取守护进程线程中的下一个帧 n = 0 while cap.isOpened(): n += 1 # _, self.imgs[index] = cap.read() cap.grab() if n == 4: # 每4帧读取一次 _, self.data[index] = cap.retrieve() if self.def_img_size is None: self.def_img_size = self.data[index].shape n = 0 time.sleep(0.01) # 等待
若是帧存在,它会像往常同样传递给letterbox函数。在frame为None的状况下,这意味着视频已被彻底处理,咱们检查列表中的全部视频是否都已被处理。若是有更多的视频要处理,cur_pos指针用于获取下一个可用视频的位置。spa
若是再也不从列表中提取视频,但仍在处理某些视频,则向其余处理组件发送一个空白帧,即,它根据其余批次中的剩余帧动态调整视频大小。
def __next__(self): self.count += 1 img0 = self.data.copy() img = [] for i, x in enumerate(img0): if x is not None: img.append(letterbox(x, new_shape=self.img_size, auto=self.rect)[0]) else: if self.cur_pos == self.n: if all( v is None for v in img0 ): cv2.destroyAllWindows() raise StopIteration else: img0[i] = np.zeros(self.def_img_size) img.append(letterbox(img0[i], new_shape=self.img_size, auto=self.rect)[0]) else: print('%g/%g: %s... ' % (self.cur_pos+1, self.n, self.sources[self.cur_pos]), end='') self.cap[i] = cv2.VideoCapture(self.sources[self.cur_pos]) fldr_end_flg = 0 while not self.cap[i].isOpened(): print('Failed to open %s' % self.sources[self.cur_pos]) self.cur_pos+=1 if self.cur_pos == self.n: img0[i] = np.zeros(self.def_img_size) img.append(letterbox(img0[i], new_shape=self.img_size, auto=self.rect)[0]) fldr_end_flg = 1 break self.cap[i] = cv2.VideoCapture(self.sources[self.cur_pos]) if fldr_end_flg: continue w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) fps = cap.get(cv2.CAP_PROP_FPS) % 100 frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) _, self.data[i] = self.cap[i].read() # 保证第一帧 img0[i] = self.data[i] img.append(letterbox(self.data[i], new_shape=self.img_size, auto=self.rect)[0]) thread = Thread(target=self.update, args=([i, self.cap[i], self.cur_pos+1]), daemon=True) print(' success (%gx%g at %.2f FPS having %g frames).' % (w, h, fps, frames)) self.cur_pos+=1 thread.start() print('') # 新的一行 # 堆叠 img = np.stack(img, 0) # 转换 img = img[:, :, :, ::-1].transpose(0, 3, 1, 2) # BGR 到 RGB, bsx3x416x416 img = np.ascontiguousarray(img) return self.sources, img, img0, None
随着大量的时间花费在数据收集和数据预处理上,我相信这有助于减小视频与模型匹配的时间,咱们能够集中精力使模型与数据相匹配。
我在这里附上完整的源代码。但愿这有帮助!
原文连接:https://towardsdatascience.co...
欢迎关注磐创AI博客站:
http://panchuang.net/
sklearn机器学习中文官方文档:
http://sklearn123.com/
欢迎关注磐创博客资源汇总站:
http://docs.panchuang.net/