-
Notifications
You must be signed in to change notification settings - Fork 377
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
"semiplanar" YUV images support #518
Comments
Hi! |
Hi Christian, This format is used internally by Chromium. Sample. Not sure this is correct or makes sense though. from PIL import Image
import numpy
def getyuv():
y = []
uv = []
with open('single_frame.yuv', 'rb') as f:
for i in range(0,1080):
row = list(f.read(1984))
y.append(row)
for i in range(0,540):
row = list(f.read(1984))
uv.append(row)
return y,uv
def convert_input(y,uv):
output = numpy.full((1080, 1984,3), (0,0,0), dtype=numpy.uint8)
for row in range(0,len(uv)):
for column in range(0,int(len(uv[row])/2)-1):
output[row*2][column*2] = (y[row*2][column*2], uv[row][column], uv[row][column+992])
output[row*2][column*2+1] = (y[row*2][column*2+1], uv[row][column], uv[row][column+992])
output[row*2+1][column*2] = (y[row*2+1][column*2], uv[row][column], uv[row][column+992])
output[row*2+1][column*2+1] = (y[row*2+1][column*2+1], uv[row][column], uv[row][column+992])
return output
y, uv = getyuv()
out = convert_input(y,uv)
# Trimming the last 64 px on each rows (garbage)
out_2 = numpy.full((1080, 1920,3), (0,0,0), dtype=numpy.uint8)
for row in range(0,len(out)):
out_2[row] = out[row][:-64]
img = Image.fromarray(out_2, mode='YCbCr')
img.show() |
Oddly enough, the memory dump of this image contains 541 rows in the YU matrix, this means can't be open directly in yuview in any case. The frame I shared in the previous comment does not contains this extra bytes. |
UPDATE: tried with many different In any case, I built my own tooling for this. Probably we can close the issue as I don't think implementing this will bring any value to the project. Here's a better script in the unlikely case someone else should run into the same issue: from PIL import Image
import numpy
import os
class Convert:
def __init__(self, column, row, stride, filename) -> None:
self.column = column
self.row = row
self.stride = stride
self.filename = filename
self.fpos = 0
if os.path.exists(self.get_destination()):
os.unlink(self.get_destination())
def get_single_frame(self):
y = []
uv = []
with open(self.filename, 'rb') as f:
if f.seek(self.fpos) == -1:
return None,None
y = list(f.read(self.stride*self.row))
if len(y)==0:
return None, None
y = numpy.array(y)
y = y.reshape(self.row, self.stride)
half_row = int(self.row/2)
uv = list(f.read(self.stride*half_row))
if len(uv)==0:
return None, None
uv = numpy.array(uv)
uv = uv.reshape(half_row, self.stride)
# Remove last line (contain garbage)
self.fpos = f.tell() + self.stride
return y,uv
def trim(self, frame):
output = numpy.full((self.row,self.column,3), (0,0,0), dtype=numpy.uint8)
for row in range(0,self.row):
trimmed_row = frame[row][:(self.column-self.stride)]
output[row] = trimmed_row
return output
def show(self, frame):
image = Image.fromarray(frame, mode='YCbCr')
image.show()
def merge_yuv(self,y,uv):
output = numpy.full((self.row, self.stride, 3), (0,0,0), dtype=numpy.uint8)
half_stride = int(self.stride/2)
uv_rows = len(uv)
for row in range(0,uv_rows):
uv_columns = len(uv[row])
half_uv_columns = int(uv_columns/2)-1
for column in range(0,half_uv_columns):
output[row*2][column*2] = (y[row*2][column*2], uv[row][column], uv[row][column+half_stride])
output[row*2][column*2+1] = (y[row*2][column*2+1], uv[row][column], uv[row][column+half_stride])
output[row*2+1][column*2] = (y[row*2+1][column*2], uv[row][column], uv[row][column+half_stride])
output[row*2+1][column*2+1] = (y[row*2+1][column*2+1], uv[row][column], uv[row][column+half_stride])
return output
def get_destination(self):
return self.filename[:self.filename.rfind('.')] + ".converted.yuv"
def save(self, y,uv):
with open(self.get_destination(), 'ab') as f:
f.write(bytes(y.reshape(y.shape[0]*y.shape[1]).tolist()))
u = uv[:,0:int(uv.shape[1]/2)]
v = uv[:,int(uv.shape[1]/2):]
f.write(bytes(u.reshape(u.shape[0]*u.shape[1]).tolist()))
f.write(bytes(v.reshape(v.shape[0]*v.shape[1]).tolist()))
target1 = 'your_file.yuv'
# change me according to target1 file spec
a = Convert(848,480,960, target1)
i=0
y, uv = a.get_single_frame()
while y is not None:
a.save(y,uv)
# Show an image every 20 frame for debug purposes
if (i+1)%20 == 0:
frame = a.merge_yuv(y,uv)
frame = a.trim(frame)
a.show(frame)
y,uv = a.get_single_frame()
i=i+1 This will take
The only things that needs to be changed according to video spec are the parameter of the
Cheers! |
I think at this point I may have tried each combination of custom and non custom decoding option but it's never displaying the image correctly. |
Ah sorry then that is not exactly the format that you are looking for. Sorry. That would have been to easy anyway. |
Yup, I already shared it here: #518 (comment) Pasting the link again here: https://github.com/IENT/YUView/files/10848045/single_frame.zip |
Ah sorry my bad I was blind. Got it! |
No worries, YW! |
Ok so I looked though all the data and files and its still a bit strange:
I have still not found any documentation of a format like this mentioned anywhere. I mean there is all sorts of strange YUV formats out there. |
Yes, I'm sure, and yes, chromium code "apparently" support normal planar YUV files. However, if you debug the media part, you'll find out that this is just illusory. A YUV file in chrome is represented as a contiguous memory area in which:
This means that in order to render the image correctly you still have to take into account that the UV plane is interleaved in this way (half row U, half row V).
Yup, this is where I'm talking about that #518 (comment) I thought I removed this extra line but perhaps I'm wrong. And yeah, I agree it's strange. ffmpeg it's not even supporting it, this is why I was saying probably it's not adding much value to the project. |
Can you refer to the code where this happens in the chromium media part please? I checked out the code and to me it looks like the I420 format has 3 separate planes. E.g. here is the code from
The offset for the V plane here is I am riding on this so much because if we find out the name of this format, then we can also use it. I don't want to invent a new name for this as there must be one if it is used in chromium. I think I found one reference to a format like this in the Microsoft docs: https://learn.microsoft.com/en-us/windows/win32/medfound/recommended-8-bit-yuv-formats-for-video-rendering#imc2 . They call it IMC2. |
From the view of data arrangement, we can divide the YUV formats into 3 basic types: Planar(3/4(alpha) plans), Semi-Plan(2 plans), Interleaved(or so called packed, only 1 plan). Then divide the Semi-Plan to 2 subclass: uv_interleaved(UVUV...UVUV), or uv_followed(UU...UUVV...VV). I believe this will work in distinguishing from YUV formats. |
Is your feature request related to a problem? Please describe.
Disclaimer: I barely know what I'm saying.
I played a lot with the configuration of YUView and seems like I'm not able to find a configuration that correctly render "semiplanar" YUV images.
I have a set of YUV frames that have a 420 chroma subsample. In these frames, Y is a plane on its own but U and V are a single plane.
The frames are
1984x1080
(that's 1080p with a stride), and the memory layout is:1984x1080
bytes matrix, followed by1984x540
bytes matrix. On each row, first 992 bytes are U and the rest is V.Is there a way to render these frames in the current implementation of YUView or is it something that can be added?
Thanks!
Describe the solution you'd like
Render a frame described above.
Describe alternatives you've considered
n.a.
The text was updated successfully, but these errors were encountered: