使用OpenCV和Python生成电影条形码

时间:2022-07-28
本文章向大家介绍使用OpenCV和Python生成电影条形码,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

点击上方"蓝色小字"关注我呀

本文来自光头哥哥的博客【Generating movie barcodes with OpenCV and Python】,仅做学习分享。

原文链接:https://www.pyimagesearch.com/2017/01/16/generating-movie-barcodes-with-opencv-and-python/

在上篇文章中,我演示了如何计算视频文件中的帧数。

今天我们将用这些知识来帮助我们完成可视化电影条形码,类似于上图的那个。

我第一次知道电影条码是在几年前,在2013年布鲁克林电影节上。

自从我开始运营PyImageSearch网站,我收到了一些关于生成电影条形码的邮件,因此我决定写一篇关于它的博客文章。毕竟,这是一个相当简介成熟的技术。

为了构建电影条码,我们需要完成三个任务:

任务1:确定视频文件中的帧数。计算电影中帧的总数可以让我们知道在电影条码可视化中应该包含多少帧。帧数太多,我们的条形码将是巨大的,帧数太少,电影条码会让人不舒服。 任务2:生成电影条码数据。一旦我们知道了我们想要包含在电影条码中的视频帧的总数,我们就可以循环遍历每个帧并计算RGB平均值,并保存到平均值列表,该列表就是我们实际的电影条码数据。

任务3:显示电影条码。给定一组帧的RGB平均值列表,我们可以使用这些数据创建显示在屏幕上的实际电影条码可视化。

程序架构

我们的项目目录结构如下:

|--- output/
|--- videos/
|--- count_frames.py
|--- generate_barcode.py
|--- visualize_barcode.py

输出目录将存储实际的电影条形码(生成的电影条形码图像和序列化的RGB平均值)。 然后我们有视频文件夹,我们的输入视频文件存在磁盘上。 最后,我们需要三个辅助脚本:count_frames.py generate_barcode.py, visualize_barcode.py。在下面的小节中,我们将讨论这些Python文件。

计算视频总帧数

在上周的博客文章中,我讨论了如何(有效地)确定视频文件中的帧数。

# import the necessary packages
from imutils.video import count_frames
import argparse
import os

# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-v", "--video", required=True,
  help="path to input video file")
ap.add_argument("-o", "--override", type=int, default=-1,
  help="whether to force manual frame count")
args = vars(ap.parse_args())
# count the total number of frames in the video file
override = False if args["override"] < 0 else True
total = count_frames(args["video"], override=override)

# display the frame count to the terminal
print("[INFO] {:,} total frames read from {}".format(total,
  args["video"][args["video"].rfind(os.path.sep) + 1:]))

顾名思义,这个脚本只是计算视频文件中的帧数。

使用OpenCV生成电影条码

现在我们知道如何确定视频文件中的帧总数——尽管我们还不清楚为什么需要知道它。 为了理解为什么在生成电影条码之前知道视频文件中的帧总数是很重要的,让我们来看看generate_barcodes.py:

# import the necessary packages
import argparse
import json
import cv2
# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-v", "--video", required=True,
  help="path to input video")
ap.add_argument("-o", "--output", required=True,
  help="path to output JSON file containing frame averages")
ap.add_argument("-s", "--skip", type=int, default=0,
  help="# of frames to skip (to create shorter barcodes)")
args = vars(ap.parse_args())

第2-4行导入所需的Python包,第6-13行解析命令行参数。

——video:这是我们要为其生成电影条码的输入视频文件的路径。 ——output:我们将对输入视频文件中的帧进行循环,并计算每帧的RGB平均值。这些RGB平均值将被序列化为一个JSON文件,因此我们可以在下一节中使用这些数据来进行实际的电影条形码可视化。 ——skip:该参数控制处理视频时要跳过的帧数。为什么我们要跳过帧呢?以《侏罗纪公园》预告片为例:一个小于3m30s的电影片段有超过4700个帧。如果我们只使用一个像素来可视化每帧的RGB平均值,我们的电影条码将超过4700像素宽!因此,我们需要跳过每n个帧来减少输出的可视化文件大小。

我们的下一个代码块初始化我们的帧平均列表,并通过cv2.VideoCapture生成一个指向我们的视频文件的指针:

# initialize the list of frame averages along with the total
# number of frames read
avgs = []
total = 0
# grab a pointer to the video file
print("[INFO] looping over frames in video (this will take awhile)...")
video = cv2.VideoCapture(args["video"])

现在我们的变量已经初始化了,我们可以对帧进行循环并计算RGB平均值:

# loop over the frames of the video
while True:
  # grab the current frame
  (grabbed, frame) = video.read()
 
  # check to see if we have reached the end of the
  # video
  if not grabbed:
    break
  # increment the total number of frames read
  total += 1
  # check to see if we should compute the average RGB value
  # of the current frame
  if args["skip"] == 0 or total % args["skip"] == 0:
    avg = cv2.mean(frame)[:3]
    avgs.append(avg)
# release the video pointer
video.release()

我们使用.read方法从视频文件中获取下一帧(第4行),并增加处理的帧总数(第11行)。 然后应用——skip命令行参数来确定当前帧是否应该包含在avgs列表中(第14行)。 如果帧应该被保留,我们计算帧的RGB平均值并更新avgs列表(第15行和第16行)。 处理完视频文件中的所有帧后,我们可以将RGB平均值序列化到磁盘:

# dump the frame averages to file
print("[INFO] saving frame averages...")
f = open(args["output"], "w")
f.write(json.dumps(avgs))
f.close()
print("[INFO] {:,} total frame averages saved".format(len(avgs)))

在《侏罗纪公园》的预告片中执行此脚本,您将看到以下输出:

现在,我们有了电影帧的RGB平均序列,打开visualize_bar .py文件,插入以下代码:

# import the necessary packages
import numpy as np
import argparse
import json
import cv2
# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-a", "--avgs", required=True,
  help="path to averages JSON file")
ap.add_argument("-b", "--barcode", required=True,
  help="path to output barcode visualization image")
ap.add_argument("-t", "--height", type=int, default=250,
  help="height of output barcode image")
ap.add_argument("-w", "--barcode-width", type=int, default=1,
  help="width of each bar in output image")
args = vars(ap.parse_args())

这个脚本需要两个命令行参数,后面跟着两个可选参数:

——avgs:这个参数是我们的序列化JSON文件的路径,该文件包含视频中每帧的平均RGB值。

——barcode:该参数提供了输出电影条码可视化图像的路径。 ——height:该参数控制电影条码可视化的高度。我们将默认这个值为250像素。 ——barcode-width:电影条形码中的每个单独的条(即RGB平均值)需要有以像素为单位的宽度。我们将默认值设置为每条1像素,但是我们可以通过为这个命令行参数提供不同的值来改变宽度。

我们现在可以进行可视化条形码:

# load the averages file and convert it to a NumPy array
avgs = json.loads(open(args["avgs"]).read())
avgs = np.array(avgs, dtype="int")
# grab the individual bar width and allocate memory for
# the barcode visualization
bw = args["barcode_width"]
barcode = np.zeros((args["height"], len(avgs) * bw, 3),dtype="uint8")
# loop over the averages and create a single 'bar' for
# each frame average in the list
for (i, avg) in enumerate(avgs):
  cv2.rectangle(barcode, (i * bw, 0), ((i + 1) * bw,
    args["height"]), avg, -1)
# write the video barcode visualization to file and then
# display it to our screen
cv2.imwrite(args["barcode"], barcode)
cv2.imshow("Barcode", barcode)
cv2.waitKey(0)

第2和3行从磁盘加载序列化的RGB平均值序列。 第27行利用--height参数以及avgs列表中的条目数和——barcode-width来为足够大的NumPy数组分配内存,以便存储电影条形码。 对于每个RGB平均值,我们分别对它们进行循环(第10行)并使用cv2.rectangle函数绘制电影条码中的每个条形(第11行和第12行)。 最后,第15-16行将电影条形码写入磁盘,并将可视化结果显示在屏幕上。

电影条形码可视化

查看《侏罗纪公园》预告片中的电影条码可视化:

$ python generate_barcode.py --video videos/jurassic_park_trailer.mp4 
  --output output/jurassic_park_trailer.json --skip 25
$ python visualize_barcode.py --avgs output/jurassic_park_trailer.json 
  --barcode output/jurassic_park_trailer.png --barcode-width 5

THE END