如何用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
相关产品推荐
相关产品推荐

