PyAV-Org / PyAV

Pythonic bindings for FFmpeg's libraries.
https://pyav.basswood-io.com/
BSD 3-Clause "New" or "Revised" License
2.47k stars 360 forks source link

Generating a video with numpy and writing it with pyav does not preserve color #1406

Closed ivan94fi closed 4 months ago

ivan94fi commented 4 months ago

$${\color{red}}$$

${{\color{red}\Huge{\textsf{ [EDIT]: this is not an issue, this code actually works }}}}\$


Overview

I am using a modified version of examples/numpy/generate_video.py to obtain a video with a clear edge.

When converting the numpy frames to video the edge between colors gets blurry, see images below.

Expected behavior

There should be a clear edge between the magenta and the green.

I expect to see something like this: 28-05-2024_15-21-26_edit

Actual behavior

[EDIT] The actual behavior is correct.

Old Content This is what I get instead, there is one pixel which has an interpolated color: ![28-05-2024_15-21-26](https://github.com/PyAV-Org/PyAV/assets/30447023/1c31dbd1-22d1-4bae-bc03-ca7bffc4586a)

Investigation

[EDIT]

Old Content I tried using different colors, encoders, container, color formats, but the issue remains. Even with a yuv444p pixel format the issue persists. When saving the single frame as an image like this (code included in the MRE below): ```python frame = av.VideoFrame.from_ndarray(img, format="rgb24") frame.to_image().save("rgb24.png") for f in ["yuv420p", "yuv422p", "yuv444p"]: frame.reformat(format=f).to_image().save(f"{f}.png") ``` the only one that is wrong is the "yuv420p" version, but this is somehow expected, I think. The 444 and 422 versions are as I expect them to be. Here is how the `yuv420p.png` saved from the script looks: ![28-05-2024_15-48-05](https://github.com/PyAV-Org/PyAV/assets/30447023/d34c34dd-46fb-4259-a2cc-f56aefb9d886) Instead, when saving the video the colors are always blended, no matter what pixel format is used.

Reproduction

import av
import numpy as np
from PIL import Image

duration = 1
fps = 50
w = 800
h = 600

# Also tried y4m with wrapped_avframe codec, ffvhuff in mkv container.
container = av.open("test.mkv", mode="w")
stream = container.add_stream("ffv1", rate=fps)

stream.width = w
stream.height = h
stream.pix_fmt = "yuv444p"  # "yuv422p", "yuv420p"

total_frames = duration * fps
for i in range(total_frames):
    img = np.full((h, w, 3), (0,231,33), dtype=np.uint8)

    img[h//2-50:h//2+50, w//2-50:w//2+50, :] = (255, 0, 255)

    frame = av.VideoFrame.from_ndarray(img, format="rgb24")

    # DEBUG: save images
    if i == 0:
        frame.to_image().save("rgb24.png")
        for f in ["yuv420p", "yuv422p", "yuv444p"]:
            frame.reformat(format=f).to_image().save(f"{f}.png")

    for packet in stream.encode(frame):
        container.mux(packet)

# Flush stream
for packet in stream.encode():
    container.mux(packet)

# Close the file
container.close()

Save and execute the script and you will find these files in the same directory: test.mkv, yuv420p.png, yuv422p.png, yuv444p.png and rgb24.png.

Then use this to generate frames from video:

ffmpeg -i test.mkv test_frame_%04d.png

When using the correct rgb24.png as input to ffmpeg binary, the generated ffv1 video does not contain the color interpolation problem:

ffmpeg -loop 1 -i rgb24.png -c:v ffv1 -pix_fmt yuv444p from_single_frame.mkv
ffmpeg -i from_single_frame.mkv from_single_frame_n_%04d.png

Versions

Research

I have done the following:

Additional context

What I was originally trying to accomplish is to generate a video where frames are composed of lines of alternating colors, to test some interlacing issues. But I stopped at the generation phase because I cannot get proper color separation.

ivan94fi commented 4 months ago

Hi, closing this as it is not a bug, I inverted the height and width variables so something bad happened. In fact, the code that I posted in this issue works correctly, as I corrected the mistake just before pasting the code here.

I will edit the issue to highlight this.

Sorry for the confusion, and thank you for the work!