python-3.x - 合成为 MP4 时 png 周围的moviepy黑色边框
问题描述
将 png 合成到 MP4 视频中会在边缘周围创建黑色边框。
这是使用moviepy 1.0.0
下面的代码复制了带有红色文本 png 的 MP4。
import numpy as np
import moviepy.editor as mped
def composite_txtpng_on_colour():
bg_color = mped.ColorClip(size=[400, 300], color=np.array([0, 255, 0]).astype(np.uint8),
duration=2).set_position((0, 0))
text_png_postition = [5, 5]
text_png = mped.ImageClip("./txtpng.png", duration=3).set_position((text_png_postition))
canvas_size = bg_color.size
stacked_clips = mped.CompositeVideoClip([bg_color, text_png], size=canvas_size).set_duration(2)
stacked_clips.write_videofile('text_with_black_border_video.mp4', fps=24)
composite_txtpng_on_colour()
结果是可以在 VLC 播放器中播放的 MP4。黑边的截图如下所示:-
任何删除黑色边框的建议将不胜感激。
更新:看起来moviepy 做了一个blit 而不是alpha 合成。
def blit(im1, im2, pos=None, mask=None, ismask=False):
""" Blit an image over another. Blits ``im1`` on ``im2`` as position ``pos=(x,y)``, using the
``mask`` if provided. If ``im1`` and ``im2`` are mask pictures
(2D float arrays) then ``ismask`` must be ``True``.
"""
if pos is None:
pos = [0, 0]
# xp1,yp1,xp2,yp2 = blit area on im2
# x1,y1,x2,y2 = area of im1 to blit on im2
xp, yp = pos
x1 = max(0, -xp)
y1 = max(0, -yp)
h1, w1 = im1.shape[:2]
h2, w2 = im2.shape[:2]
xp2 = min(w2, xp + w1)
yp2 = min(h2, yp + h1)
x2 = min(w1, w2 - xp)
y2 = min(h1, h2 - yp)
xp1 = max(0, xp)
yp1 = max(0, yp)
if (xp1 >= xp2) or (yp1 >= yp2):
return im2
blitted = im1[y1:y2, x1:x2]
new_im2 = +im2
if mask is None:
new_im2[yp1:yp2, xp1:xp2] = blitted
else:
mask = mask[y1:y2, x1:x2]
if len(im1.shape) == 3:
mask = np.dstack(3 * [mask])
blit_region = new_im2[yp1:yp2, xp1:xp2]
new_im2[yp1:yp2, xp1:xp2] = (1.0 * mask * blitted + (1.0 - mask) * blit_region)
return new_im2.astype('uint8') if (not ismask) else new_im2
所以,Rotem 是对的。
new_im2[yp1:yp2, xp1:xp2] = (1.0 * mask * blitted + (1.0 - mask) * blit_region)
是
(alpha * img_rgb + (1.0 - alpha) * bg)
这就是moviepy合成的方式。这就是为什么我们在边缘看到黑色的原因。
解决方案
主要问题是YUV420颜色子采样,但这也是压缩伪影和不完美的“文本红色”图像的结果。
图像缺陷只是在 Alpha 通道中。
文本周围的 alpha(透明度)值不是 255,也不是 0(半透明)像素。
以下代码示例对其进行了更正,并显示了差异(使用 OpenCV):
orig_img = cv2.imread('txtpng.png', cv2.IMREAD_UNCHANGED)
img = orig_img.copy()
img[(img != 255) & (img != 0)] = 255 # Keep only two values: 0 and 255
cv2.imwrite('txtpng2.png', img) # Write img to txtpng2.png
cv2.imshow('alpha diff', cv2.absdiff(orig_img[:,:,3], img[:,:,3])*100) # Show the difference in alpha channels
cv2.waitKey()
cv2.destroyAllWindows()
上面的代码只保留了两个值: 0 和 255 in img
。
设置编解码器参数:
作为参考,我创建了一个未压缩的 AVI 视频文件:
# Save uncompressed AVI as reference
stacked_clips.write_videofile('text_with_black_border_video.avi', fps=24, codec='rawvideo', ffmpeg_params=['-pix_fmt', 'bgr24'])
我还尝试选择yuv444p
像素格式的 H.264 编解码器,但由于某种原因它不起作用。
我选择了 H.265 编解码器。
使用ffmpeg_params
,我还设置'-crf', '10'
了几乎无损的视频压缩:
stacked_clips.write_videofile('text_with_black_border_video.mp4', fps=24, codec='libx265', ffmpeg_params=['-pix_fmt', 'yuv444p', '-crf', '10'])
这是完整的代码示例:
import numpy as np
import moviepy.editor as mped
import cv2
orig_img = cv2.imread('txtpng.png', cv2.IMREAD_UNCHANGED)
img = orig_img.copy()
img[(img != 255) & (img != 0)] = 255 # Keep only two values: 0 and 255
cv2.imwrite('txtpng2.png', img)
cv2.imshow('img', img)
cv2.imshow('alpha diff', cv2.absdiff(orig_img[:,:,3], img[:,:,3])*100) # Show the difference in alpha channels
cv2.waitKey()
cv2.destroyAllWindows()
def composite_txtpng_on_colour():
bg_color = mped.ColorClip(size=[400, 300], color=np.array([0, 255, 0]).astype(np.uint8),
duration=2).set_position((0, 0))
text_png_postition = [5, 5]
text_png = mped.ImageClip('txtpng2.png', duration=3).set_position((text_png_postition))
canvas_size = bg_color.size
stacked_clips = mped.CompositeVideoClip([bg_color, text_png], size=canvas_size).set_duration(2)
stacked_clips.write_videofile('text_with_black_border_video.mp4', fps=24, codec='libx265', ffmpeg_params=['-pix_fmt', 'yuv444p', '-crf', '10'])
# Save uncompressed AVI as reference
stacked_clips.write_videofile('text_with_black_border_video.avi', fps=24, codec='rawvideo', ffmpeg_params=['-pix_fmt', 'bgr24'])
composite_txtpng_on_colour()
结果(text_with_black_border_video.mp4
):
参考(未压缩text_with_black_border_video.avi
):
笔记:
- 我正在使用moviepy 1.03版
我弄清楚了为什么 H.264 编解码器不适用于像素格式yuv444p
。
我们可以添加'-report'
到列表中ffmpeg_params
:
stacked_clips.write_videofile('text_with_black_border_video.mp4', fps=24, codec='libx264', ffmpeg_params=['-pix_fmt', 'yuv444p', '-crf', '10', '-report'])
记录的报告以:
... \\lib\\site-packages\\imageio_ffmpeg\\binaries\\ffmpeg-win64-v4.2.2.exe" -y -loglevel error -f rawvideo -vcodec rawvideo -s 400x300 -pix_fmt rgb24 -r 24.00 -an -i - -vcodec libx264 -preset medium -pix_fmt yuv444p -crf 10 -report -pix_fmt yuv420p text_with_black_border_video.mp4
日志显示moviepy 添加了-pix_fmt yuv420p
参数。
pix_fmt
参数被添加两次:-pix_fmt yuv444p
-pix_fmt yuv420p
.
论点“yuv420p
获胜”。
更新:
保持边缘稍微模糊的方法:
边缘颜色不是背面,而是深绿色。
边缘颜色是RGB 颜色空间中Alpha 合成的结果。
我想合成是使用简单的公式执行的:
dst_img = alpha*img_rgb + (1-alpha)*bg
哪里alpha = img[:,:,0:3]/255
。
应用上述公式时,我们得到以下图像:
建议的解决方案:在LAB颜色空间
中应用 alpha 合成。
优势:
与 RGB 和 CMYK 颜色模型不同,CIELAB 旨在接近人类视觉。
LAB色彩空间中的线性运算被人类视觉视为(近似)线性。
这是在 LAB 颜色空间中进行 alpha 合成的代码示例:
img = cv2.imread('txtpng.png', cv2.IMREAD_UNCHANGED)
img2 = np.zeros((300, 400, 4), np.uint8)
img2[(300-img.shape[0])//2:(300+img.shape[0])//2, (400-img.shape[1])//2:(400+img.shape[1])//2, :] = img
alpha = img2[:, :, 3].astype(np.float64)/255 # Convert alpha to range [0, 1]
alpha = np.dstack((alpha, alpha, alpha)) # Duplicate alpha to 3 channels
img2 = img2[:, :, 0:3] # Only BGR without alpha
bg = np.full_like(img2, (0, 255, 0)) # Green background
bg = cv2.cvtColor(bg, cv2.COLOR_BGR2LAB)
img2 = cv2.cvtColor(img2, cv2.COLOR_BGR2LAB)
composed_img = (img2.astype(np.float64)*alpha + bg.astype(np.float64)*(1-alpha)).astype(np.uint8)
composed_img = cv2.cvtColor(composed_img, cv2.COLOR_LAB2BGR)
cv2.imwrite('composed_img.png', composed_img) # Store the result
注意:
我在 LAB 颜色空间中找不到任何关于 alpha 合成的论文(但我看起来并不难)。
推荐阅读
- c++ - cublasGemmEx 结果始终为零
- python - 在 Oracle 中使用以下划线开头的小写名称列时出现 SQLalchemy 数据库错误
- python - 如何在 BeautifulSoup 中获取没有特定标签的文本?
- java - Android 11 上 imageCapture 用例的 camerax“未绑定到有效相机”
- angular - 如何在Angular中的组件之间共享静态变量
- python - 当缩进完全正确时,为什么这个 Python 代码会给我一个关于缩进的错误
- excel - 如何根据变量写入一系列单元格?
- bash - sed 通过文件而不使用 for 循环?
- regex - 如何从 Regex 表达式中排除特定数字?
- python - 遍历字典,做同样的事情,如何优化?