B站视频特征分析|Bilibili数据洞察 视频特征提取

本文将介绍一段用于处理视频文件并提取其亮度、对比度、颜色等信息的代码。首先,我们将对需求进行分析,然后介绍代码的主要功能,接着对代码进行详细解析,最后给出注意事项以及完整代码。

这是 Bilibili 数据分析项目的一部分。

需求分析

随着多媒体技术的发展,视频数据的处理需求越来越广泛。在处理视频数据时,我们需要获取视频的各种属性,如亮度、对比度、颜色等。本代码的主要目的就是从给定的视频文件中提取这些信息,并将结果保存为CSV文件。

功能介绍

本代码具有以下功能:

  1. 读取视频文件,并获取其基本属性,如帧数、宽度、高度等。
  2. 对视频进行逐帧处理,计算每一帧的亮度、对比度、颜色等信息。
  3. 将处理结果按秒进行分组,并计算每秒的平均值。
  4. 将处理结果保存为CSV文件。

主要代码介绍

  1. 首先,通过 cv2 库读取视频文件,并获取视频的基本属性,如帧数、宽度、高度等。
  2. 设定跳帧处理策略,以减少处理时间并降低计算负担。在这里,我们设置每5帧进行一次处理。
  3. 使用for循环逐帧处理视频,获取每一帧的亮度、对比度、颜色等信息。
    • 将每一帧从BGR色彩空间转换为灰度色彩空间和HSV色彩空间。
    • 计算每一帧的亮度均值、最大值和最小值。
    • 计算每一帧的对比度和饱和度均值。
    • 计算每一帧的纹理信息。
    • 计算每一帧的颜色熵。
  4. 将处理结果保存到data_list中。
  5. data_list转换为pandas数据帧,并按照秒进行分组,计算每秒的平均值。
  6. 将处理结果保存为CSV文件。
  7. 使用线程池(ThreadPoolExecutor)并发处理多个视频文件,提高处理速度。

关键代码

在本节中,我们将重点介绍上述代码中的关键部分。

读取视频文件

首先,我们需要读取视频文件,并获取其基本属性。这里,我们使用 cv2.VideoCapture 类来读取视频文件,并通过 cv2.CAP_PROP_* 系列属性获取视频的帧数、宽度、高度等。

cap = cv2.VideoCapture(video_path)

fps = cap.get(cv2.CAP_PROP_FPS)
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))

计算亮度、对比度和颜色信息

对于每一帧,我们首先将其从BGR色彩空间转换为灰度色彩空间和HSV色彩空间。

frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)

接着,我们计算每一帧的亮度均值、最大值和最小值。

brightness_mean = np.mean(frame)
brightness_max = np.max(frame)
brightness_min = np.min(frame)

然后,计算每一帧的对比度和饱和度均值。

contrast = np.std(frame_gray)
saturation_mean = np.mean(hsv[:, :, 1])

为了获取纹理信息,我们使用Laplacian算子对灰度图像进行处理,并计算其均值。

gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
laplacian = cv2.Laplacian(gray, cv2.CV_64F)
texture = np.mean(laplacian)

最后,计算每一帧的颜色熵。首先,我们将图像分为三个通道(蓝色、绿色、红色),然后计算每个通道的直方图,并对其进行归一化。接着,计算颜色熵。

b, g, r = cv2.split(frame)
hist_b = cv2.calcHist([b], [0], None, [256], [0, 256])
hist_g = cv2.calcHist([g], [0], None, [256], [0, 256])
hist_r = cv2.calcHist([r], [0], None, [256], [0, 256])
hist_b = hist_b / np.sum(hist_b)
hist_g = hist_g / np.sum(hist_g)
hist_r = hist_r / np.sum(hist_r)
color_entropy = -np.sum(hist_b * np.log2(hist_b + 1e-7)) \
                - np.sum(hist_g * np.log2(hist_g + 1e-7)) \
                - np.sum(hist_r * np.log2(hist_r + 1e-7))

数据汇总与保存

将处理后的数据添加到 data_list,并将其转换为 pandas 数据帧。然后,我们按照秒进行分组,并计算每秒的平均值。

df = pd.DataFrame(data_list)
df_grouped = df.groupby('second').mean().reset_index()

最后,将处理结果保存为CSV文件。

df_grouped.to_csv(GALLERY_PATH + '{}/{}_1fps.csv'.format(bvid, bvid), index=False)

并发处理多个视频文件

为了提高处理速度,我们使用线程池(ThreadPoolExecutor)并发处理多个视频文件。首先,我们创建一个线程池,并提交任务到线程池。

with ThreadPoolExecutor(max_workers=4) as executor:
    futures = [executor.submit(process_video, bvid) for bvid in tqdm(bvid_list)]

接着,我们使用 tqdm 进度条显示处理进度,并等待所有任务完成。

for future in tqdm(concurrent.futures.as_completed(futures), total=len(futures)):
    pass

以上便是本代码的重点部分介绍。在实际应用中,可以根据需要对代码进行适当的调整,以满足特定场景的需求。例如,可以调整跳帧处理策略、修改特征提取方法或者优化线程池参数。

在使用本代码时,除了注意安装所需的第三方库,还需要确保视频文件路径和CSV文件保存路径与实际情况相符。此外,根据实际计算机配置,可以适当调整线程池的最大并发线程数(max_workers 参数),以实现较好的性能平衡。

总之,本代码通过提取视频文件中的亮度、对比度、颜色等信息,为进一步的视频处理和分析提供了基础数据。借助并发处理技术,本代码还能在有限的时间内处理大量视频文件,从而提高处理效率。

注意事项

  1. 在使用本代码之前,需要安装 cv2numpypandastqdmconcurrent.futures 等第三方库。
  2. 本代码设定了跳帧处理策略,这可以减少处理时间并降低计算负担。但是,需要注意的是,跳帧处理可能导致部分信息丢失。根据实际需求,可以适当调整 skip_frames 参数的值。
  3. 本代码使用线程池并发处理多个视频文件。max_workers 参数决定了最大并发线程数。需要注意的是,过高的并发线程数可能导致计算机性能下降。根据实际计算机配置,可以适当调整 max_workers 参数的值。
  4. 本代码处理的视频文件路径和CSV文件保存路径需要根据实际情况进行修改。

完整代码

import cv2  
import numpy as np  
import pandas as pd  
from tqdm import tqdm  
import os  
from concurrent.futures import ThreadPoolExecutor  
import concurrent.futures  

GALLERY_PATH = '/Volumes/SSD/Data/video/'  

video_list = pd.read_csv('/Volumes/SSD/Data/getVideoinfo/getVideoinfo_byhot_6.csv')  
bvid_list = video_list['bvid'].values.tolist()  
cid_list = video_list['cid'].values.tolist()  

def process_video(bvid):  
    video_path = GALLERY_PATH + '{}/{}.mp4'.format(bvid, bvid)  

    if not os.path.exists(video_path):  
        return  

    cap = cv2.VideoCapture(video_path)  

    fps = cap.get(cv2.CAP_PROP_FPS)  
    total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))  
    width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))  
    height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))  

    data_list = []  

    # 跳帧处理  
    skip_frames = 5  

    # 处理每一帧并记录亮度和对比度信息  
    for i in range(0, total_frames, skip_frames):  
        ret, frame = cap.read()  

        if not ret:  
            break  

        frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)  
        hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)  

        brightness_mean = np.mean(frame)  
        brightness_max = np.max(frame)  
        brightness_min = np.min(frame)  

        contrast = np.std(frame_gray)  
        saturation_mean = np.mean(hsv[:, :, 1])  

        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)  
        laplacian = cv2.Laplacian(gray, cv2.CV_64F)  
        texture = np.mean(laplacian)  

        b, g, r = cv2.split(frame)  
        hist_b = cv2.calcHist([b], [0], None, [256], [0, 256])  
        hist_g = cv2.calcHist([g], [0], None, [256], [0, 256])  
        hist_r = cv2.calcHist([r], [0], None, [256], [0, 256])  
        hist_b = hist_b / np.sum(hist_b)  
        hist_g = hist_g / np.sum(hist_g)  
        hist_r = hist_r / np.sum(hist_r)  
        color_entropy = -np.sum(hist_b * np.log2(hist_b + 1e-7)) \  
                        - np.sum(hist_g * np.log2(hist_g + 1e-7)) \  
                        - np.sum(hist_r * np.log2(hist_r + 1e-7))  

        data_list.append({  
            'frame': i,  
            'second': int(i // fps),  
            'brightness_mean': brightness_mean,  
            'brightness_max': brightness_max,  
            'brightness_min': brightness_min,  
            'saturation_mean': saturation_mean,  
            'texture': texture,  
            'contrast': contrast,  
            'color_entropy': color_entropy  
        })  

    df = pd.DataFrame(data_list)  
    df_grouped = df.groupby('second').mean().reset_index()  
    df_grouped.to_csv(GALLERY_PATH + '{}/{}_1fps.csv'.format(bvid, bvid), index=False)  

# 使用线程池  
with ThreadPoolExecutor(max_workers=4) as executor:  
    # 提交任务到线程池  
    futures = [executor.submit(process_video, bvid) for bvid in tqdm(bvid_list)]  

    # 等待所有任务完成  
    for future in tqdm(concurrent.futures.as_completed(futures), total=len(futures)):  
        pass

Related Posts