You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Python精准检测视频的Letterboxing与Pillarboxing占比

问题:视频Letterboxing/Pillarboxing检测占比计算不准确

我要开发一款工具,判断视频是否存在Letterboxing(上下黑/白边)或Pillarboxing(左右黑/白边),并检测这类边框占比是否超过10%。边框判定规则是像素值<5为黑、>250为白。我已经写了带GUI的视频上传脚本,但计算出的占比结果不准确,代码如下:

from turtle import width
import cv2
import numpy as np
import tkinter as tk
from tkinter import filedialog

# Global variable to hold the result label
result_label = None

def calculate_border_size(video_path, border_pixels=20, black_threshold=5, white_threshold=250):
    # Open video capture
    cap = cv2.VideoCapture(video_path)

    # Check if the video capture is successfully opened
    if not cap.isOpened():
        raise ValueError("Error: Could not open video file.")

    # Initialize variables to store cumulative results
    black_rows_sum = 0
    white_rows_sum = 0
    frame_count = 0
    height, width = None, None

    while True:
        ret, frame = cap.read()

        if not ret:
            break

        if height is None or width is None:
            height, width = frame.shape[:2]

        # Extract the border regions
        border_regions = np.concatenate((frame[:, :border_pixels], frame[:, -border_pixels:]), axis=1)

        # Count black and white rows in the border regions
        black_rows = np.sum(np.sum(border_regions < black_threshold, axis=0) == frame.shape[0])
        white_rows = np.sum(np.sum(border_regions > white_threshold, axis=0) == frame.shape[0])

        # Accumulate results
        black_rows_sum += black_rows
        white_rows_sum += white_rows
        frame_count += 1

    cap.release()

    # Calculate the average black and white rows
    avg_black_rows = black_rows_sum / frame_count
    avg_white_rows = white_rows_sum / frame_count

   # Calculate the percentage of black rows compared to the whole screen height
    percentage_black_rows = (avg_black_rows / height) * 100
    percentage_white_rows = (avg_white_rows / height) * 100

    # Check if the percentage of black rows exceeds 20% of the screen height
    is_letterboxing = percentage_black_rows > 20 or percentage_white_rows > 20

    return is_letterboxing, percentage_white_rows, percentage_black_rows

# Function to handle the video upload button
def upload_video():
    video_path = filedialog.askopenfilename(filetypes=[("Video files", "*.mp4;*.avi")])
    if video_path:
        analyze_video(video_path)

# Function to analyze the video and display results
def analyze_video(video_path):
    global result_label  # Declare result_label as a global variable
    try:
        is_letterboxing, percentage_white_rows, percentage_black_rows = calculate_border_size(video_path)
        result_label.config(text=f"Is Letterboxing: {is_letterboxing}\nPercentage Black Rows: {percentage_black_rows}\nPercentage White Rows: {percentage_white_rows}")
    except ValueError as e:
        result_label.config(text=str(e))

# GUI setup
root = tk.Tk()
root.title("Video Analysis")

# Button to upload video
upload_button = tk.Button(root, text="Upload Video", command=upload_video)
upload_button.pack(pady=20)

# Label to display results
result_label = tk.Label(root, text="")
result_label.pack()

# Run the GUI
root.mainloop()

问题分析与修复方案

原代码存在几个核心问题导致占比计算错误:

  • 混淆行/列统计逻辑:统计列数却除以视频高度,比例计算完全错误
  • 边框区域提取不全:只提取左右边框,未处理上下边框,无法检测Letterboxing
  • 阈值判定过于严格:要求整列所有像素都达标才判定为边框,忽略实际存在的少量噪点
  • 判定阈值不符需求:代码用20%作为阈值,但需求是检测超过10%的情况

修改后的完整代码

import cv2
import numpy as np
import tkinter as tk
from tkinter import filedialog

# Global variable to hold the result label
result_label = None

def calculate_border_size(video_path, border_pixels=20, black_threshold=5, white_threshold=250, threshold_percent=10):
    # Open video capture
    cap = cv2.VideoCapture(video_path)

    if not cap.isOpened():
        raise ValueError("Error: Could not open video file.")

    # Initialize variables to store cumulative results
    total_black_pillar = 0
    total_white_pillar = 0
    total_black_letter = 0
    total_white_letter = 0
    frame_count = 0
    height, width = None, None

    while True:
        ret, frame = cap.read()
        if not ret:
            break

        if height is None or width is None:
            height, width = frame.shape[:2]
            # 转换为灰度图简化计算
            gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        else:
            gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

        # ---------------------- 检测Pillarboxing(左右边框) ----------------------
        # 提取左右边框区域
        left_border = gray[:, :border_pixels]
        right_border = gray[:, -border_pixels:]
        pillar_regions = np.concatenate([left_border, right_border], axis=1)

        # 统计黑色/白色像素占比
        black_pillar_pixels = np.sum(pillar_regions < black_threshold)
        white_pillar_pixels = np.sum(pillar_regions > white_threshold)
        total_pillar_pixels = pillar_regions.size

        # 计算当前帧左右边框占比
        black_pillar_ratio = (black_pillar_pixels / total_pillar_pixels) if total_pillar_pixels > 0 else 0
        white_pillar_ratio = (white_pillar_pixels / total_pillar_pixels) if total_pillar_pixels > 0 else 0

        # ---------------------- 检测Letterboxing(上下边框) ----------------------
        # 提取上下边框区域
        top_border = gray[:border_pixels, :]
        bottom_border = gray[-border_pixels:, :]
        letter_regions = np.concatenate([top_border, bottom_border], axis=0)

        # 统计黑色/白色像素占比
        black_letter_pixels = np.sum(letter_regions < black_threshold)
        white_letter_pixels = np.sum(letter_regions > white_threshold)
        total_letter_pixels = letter_regions.size

        # 计算当前帧上下边框占比
        black_letter_ratio = (black_letter_pixels / total_letter_pixels) if total_letter_pixels > 0 else 0
        white_letter_ratio = (white_letter_pixels / total_letter_pixels) if total_letter_pixels > 0 else 0

        # 累加结果
        total_black_pillar += black_pillar_ratio
        total_white_pillar += white_pillar_ratio
        total_black_letter += black_letter_ratio
        total_white_letter += white_letter_ratio
        frame_count += 1

    cap.release()

    if frame_count == 0:
        raise ValueError("Error: No frames were read from the video.")

    # 计算平均占比并转换为百分比
    avg_black_pillar = (total_black_pillar / frame_count) * 100
    avg_white_pillar = (total_white_pillar / frame_count) * 100
    avg_black_letter = (total_black_letter / frame_count) * 100
    avg_white_letter = (total_white_letter / frame_count) * 100

    # 判断是否超过设定阈值
    has_pillarboxing = avg_black_pillar > threshold_percent or avg_white_pillar > threshold_percent
    has_letterboxing = avg_black_letter > threshold_percent or avg_white_letter > threshold_percent

    return has_letterboxing, has_pillarboxing, avg_black_letter, avg_white_letter, avg_black_pillar, avg_white_pillar

# Function to handle the video upload button
def upload_video():
    video_path = filedialog.askopenfilename(filetypes=[("Video files", "*.mp4;*.avi")])
    if video_path:
        analyze_video(video_path)

# Function to analyze the video and display results
def analyze_video(video_path):
    global result_label
    try:
        has_letterboxing, has_pillarboxing, avg_black_letter, avg_white_letter, avg_black_pillar, avg_white_pillar = calculate_border_size(video_path)
        result_text = (
            f"存在Letterboxing(上下边): {has_letterboxing}\n"
            f"黑色上下边占比: {avg_black_letter:.2f}%\n"
            f"白色上下边占比: {avg_white_letter:.2f}%\n\n"
            f"存在Pillarboxing(左右边): {has_pillarboxing}\n"
            f"黑色左右边占比: {avg_black_pillar:.2f}%\n"
            f"白色左右边占比: {avg_white_pillar:.2f}%"
        )
        result_label.config(text=result_text)
    except ValueError as e:
        result_label.config(text=str(e))

# GUI setup
root = tk.Tk()
root.title("视频黑边/白边检测工具")

# Button to upload video
upload_button = tk.Button(root, text="上传视频", command=upload_video)
upload_button.pack(pady=20)

# Label to display results
result_label = tk.Label(root, text="", justify=tk.LEFT)
result_label.pack(padx=20, pady=10)

# Run the GUI
root.mainloop()

修复说明

  • 修正逻辑混淆:分别统计上下/左右边框的像素占比,使用对应区域总像素数作为分母,比例计算准确
  • 新增上下边框检测:添加top_border和bottom_border的提取与统计,支持Letterboxing检测
  • 优化阈值判定:统计边框区域内符合条件的像素占比,不再要求整行/整列像素达标,适配实际场景
  • 匹配需求阈值:将判定阈值改为10%,可通过threshold_percent参数灵活调整
  • 灰度图简化计算:将帧转换为灰度图,减少计算量的同时不影响黑白像素判定
  • 优化结果展示:分别展示上下/左右边框的检测结果,数据更清晰直观

内容的提问来源于stack exchange,提问作者Lea

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 08:04:51