First let me state the context for this issue. I'm using PySide6 to capture the screen, and I want to intercept the video feed and perform some image processing on the frames using opencv. I am able to collect a QVideoFrame, convert to QImage, then convert that to a numpy array and do my image processing. However, I also want to be able to pass that numpy array back to an output video stream so I can see the results of the image processing.
I'm able to convert the numpy array into a QImage with this code;
arr = cv2.cvtColor(arr, cv2.COLOR_BGR2RGBA)
image = QImage(arr.data, arr.shape[1], arr.shape[0], QImage.Format.Format_RGBA8888)
I can then start to create a QVideoFrame like so;
format_ = QVideoFrameFormat(
image.size(),
QVideoFrameFormat.pixelFormatFromImageFormat(image.format())
)
frame2 = QVideoFrame(format_)
frame2.map(QVideoFrame.ReadWrite)
So far so good... but the next step is to copy the bytes from the QImage into the memory reserved for the QVideoFrame. There is this example that shows how to do that in C++. I tried to get this working in python using the ctypes library like this;
ctypes.memmove(
frame2.bits(0)[0],
image.bits()[0],
image.sizeInBytes()
)
This is where I got stuck. Basically QImage.bits()
and QVideoFrame.bits()
both return a memoryview object which I assume contains the actually data in bytes that I need to copy over. The issues with that code snippet above is that bits()[0]
always returns zero which raises an error from attempting to access memory that is out of bounds. I think it needs to return the pointer of that memory as an integer. I have seen various suggestions that in CPython id(object)
will give the pointer of an object, however I am not using CPython - so the question is how to do this in regular python?
For reference, my project is using python 3.9.10 on Windows 11 Home version 23H2.
Thanks to @ekhumoro for helping me work this one out. Given a numpy array you can convert it back to a QVideoFrame like so;
arr = cv2.cvtColor(arr, cv2.COLOR_BGR2RGBA)
image = QImage(arr.data, arr.shape[1], arr.shape[0], QImage.Format.Format_RGBA8888)
# convert back to QVideoFrame
format_ = QVideoFrameFormat(
image.size(),
QVideoFrameFormat.pixelFormatFromImageFormat(image.format())
)
frame2 = QVideoFrame(format_)
frame2.map(QVideoFrame.ReadWrite)
# instead of using ctypes, we can cast the memoryview of the QImage
# back on to the new QVideoFrame as long as we find the right end index.
end = len(image.bits().tobytes())
frame2.bits(0)[:end] = image.bits()
frame2.unmap()