I am writing MP4 video files with the following PyAV-based code (getting input frames represented as numpy arrays - the sort produced by imageio.imread - as input):
class MP4:
def __init__(self, fname, width, height, fps):
self.output = av.open(fname, 'w', format='mp4')
self.stream = self.output.add_stream('h264', str(fps))
self.stream.width = width
self.stream.height = height
# these 2 lines can be removed and the problem still reproduces:
self.stream.pix_fmt = 'yuv420p'
self.stream.options = {'crf': '17'}
def write_frame(self, pixels):
frame = av.VideoFrame.from_ndarray(pixels, format='rgb24')
packet = self.stream.encode(frame)
self.output.mux(packet)
def close(self):
packet = self.stream.encode(None)
self.output.mux(packet)
self.output.close()
The colors in the output MP4 video are slightly different (apparently darker) than the colors in the input images:
Screen grab of an image viewer showing an input frame:
Screen grab of VLC playing the output MP4 video:
How can this problem be fixed? I variously fiddled with the frame.colorspace attribute, stream options and VideoFrame.reformat but it changed nothing; of course I could have been fiddling incorrectly.
As you can see, the input has simple flat color regions, so I doubt it's any sort of compression artifact, eg YUV420 dropping some of the chroma info or other such.
Adding frame = frame.reformat(format='yuv420p', dst_colorspace=av.video.reformatter.Colorspace.ITU709)
before the call to encode fixes the problem. I don't know why both the format and the dst_colorspace arguments are needed for this to work, empirically they are.