The shape
attribute of a numpy.array
is the number of elements in each dimension. The first element is the height, the second the width and the third the number of channels.
A pygame.Surface
can be generated by pygame.image.frombuffer
. The 1st argument can be a numpy.array
and the 2nd argument is the format (RGB
or RGBA
).
Get the size (widht, height) for the pygame.Surface
object by slicing:
size = cvImage.shape[1::-1]
Determine the target format for the pygame.Surface
object, depending on the third channel:
format="RGBA" if cvImage.shape[2] == 4 else 'RGB'
Since the source format is BGR or BGRA, but the target format is RGB or RGBA, the red and blue channels have to be swapped:
cvImage[:, :, [0, 2]] = cvImage[:, :, [2, 0]]
In the case of a grayscale image, the shape of the array must be changed using numpy.reshape
and the gray channel must be expanded to a red-green and blue color channel using numpy.repeat
:
cvImage = np.repeat(cvImage.reshape(size[1], size[0], 1), 3, axis = 2)
With his data the pygame.Surface
object can be generated by pygame.image.frombuffer
:
surface = pygame.image.frombuffer(cvImage.flatten(), size, format)
To ensure that the image has the same pixel format as the display Surface and for optimal performance, the Surface should be converted with either convert
or convert_alpha
:
surface = surface.convert_alpha() if format == 'RGBA' else surface.convert()
Complete function cvImageToSurface
:
def c2ImageToSurface(cvImage):
if cvImage.dtype.name == 'uint16':
cvImage = (cvImage / 256).astype('uint8')
size = cvImage.shape[1::-1]
if len(cvImage.shape) == 2:
cvImage = np.repeat(cvImage.reshape(size[1], size[0], 1), 3, axis = 2)
format="RGB"
else:
format="RGBA" if cvImage.shape[2] == 4 else 'RGB'
cvImage[:, :, [0, 2]] = cvImage[:, :, [2, 0]]
surface = pygame.image.frombuffer(cvImage.flatten(), size, format)
return surface.convert_alpha() if format == 'RGBA' else surface.convert()
Minimal example:
import os
import pygame
import cv2 as cv
import numpy as np
def cvImageToSurface(cvImage):
if cvImage.dtype.name == 'uint16':
cvImage = (cvImage / 256).astype('uint8')
size = cvImage.shape[1::-1]
if len(cvImage.shape) == 2:
cvImage = np.repeat(cvImage.reshape(size[1], size[0], 1), 3, axis = 2)
format="RGB"
else:
format="RGBA" if cvImage.shape[2] == 4 else 'RGB'
cvImage[:, :, [0, 2]] = cvImage[:, :, [2, 0]]
surface = pygame.image.frombuffer(cvImage.flatten(), size, format)
return surface.convert_alpha() if format == 'RGBA' else surface.convert()
pygame.init()
window = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()
cvImage1 = cv.imread('woodtiles.jpg', cv.IMREAD_GRAYSCALE)
cvImage2 = cv.imread('woodtiles.jpg', cv.IMREAD_UNCHANGED)
cvImage3 = cv.imread('Apple1-256.png', cv.IMREAD_UNCHANGED)
pygameSurface1 = cvImageToSurface(cvImage1)
pygameSurface2 = cvImageToSurface(cvImage2)
pygameSurface3 = cvImageToSurface(cvImage3)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.fill(0)
window.blit(pygameSurface1, pygameSurface1.get_rect(topleft = window.get_rect().inflate(-10, -10).topleft))
window.blit(pygameSurface2, pygameSurface2.get_rect(center = window.get_rect().center))
window.blit(pygameSurface3, pygameSurface3.get_rect(bottomright = window.get_rect().inflate(-10, -10).bottomright))
pygame.display.flip()
pygame.quit()