0

I am trying to process images from Unity3D WebCamTexture graphics format(ARGB32) using OpenCV Python. But I am having trouble interpreting the image on the Open CV side. The image is all Blue (possibly due to ARGB)

try:
    while(True):
        data = sock.recv(480 * 640 * 4)
        if(len(data) == 480 * 640 * 4):
            image = numpy.fromstring(data, numpy.uint8).reshape( 480, 640, 4 )
            #imageNoAlpha = image[:,:,0:2]
            cv2.imshow('Image', image) #further do image processing
            
            key = cv2.waitKey(1) & 0xFF
            if key == ord("q"):
                break
finally:
    sock.close()

enter image description here

Thinkal VB
  • 189
  • 3
  • 12
  • Have you looked at the bytes to make sure you're getting what you think you are? You're absolutely sure it's ARGB, 8 bits per component, with no headers? – Tim Roberts Oct 18 '21 at 06:09
  • Yes I got the graphics format as 88 https://stackoverflow.com/questions/66706477/unity3d-webcamtexture-graphicsformat-value-doesnt-exist and the width and height of the texture is 640 * 480 and the size of the data is 480*640*4 = 1228800 (so everything adds up) – Thinkal VB Oct 18 '21 at 06:17
  • Of course the data is 1,228,800; that's what you asked for. TCP is not a packet protocol, it's a stream protocol. If the other end sent you 4 different images of 640x480x1, for example, you'd still get 1,228,800 bytes. You're going to need to dump the first set of bytes and make sure it looks like pixel data. – Tim Roberts Oct 18 '21 at 06:22
  • @TimRoberts I checked, but that's not the case. I have attached the output image. The pipes are red in colour!!. – Thinkal VB Oct 18 '21 at 06:34
  • OK, let me point out that you said "the image is all Blue". Your image is very definitely NOT "all Blue". If you had posted this image to begin with, we could have identified the BGR/RGB inversion immediately. – Tim Roberts Oct 18 '21 at 17:37

3 Answers3

3

The reason is because of the order of the channels. I think the sender read image as a RGB image and you show it as a BGR image or vice versa. Change the order of R and B channels will solve the problem:

image = image[..., [0,3,2,1]] # swap 3 and 1 represent for B and R

You will meet this problem frequently if you work with PIL.Image and OpenCV. The PIL.Image will read the image as RGB and cv2 will read as BGR, that's why all the red points in your image become blue.

CuCaRot
  • 1,208
  • 7
  • 23
1

OpenCV uses BGR (BGRA when including alpha) ordering when working with color images [1][2], this applies to images read/written with imread(), imwrite(); images acquired with VideoCapture; drawing functions ellipse(), rectangle(); and so on. This convention is self-consistent within the library, if you read an image with imread() and show it with imshow(), the correct colors will appear.

OpenCV is the only library I know that uses this ordering, e.g. PIL and Matplotlib both use RGB. If you want to convert from one color space to another use cvtColor(), example:

# Convert RGB to BGR.
new_image = cvtColor(image, cv2.COLOR_RGB2BGR)

See the ColorConversionCodes enum for all supported conversion pairs. Unfortunately there is no ARGB to BGR, but you can always manually manipulate the NumPy array anyway:

# Reverse channels ARGB to BGRA.
image_bgra = image[..., ::-1]

# Convert ARGB to BGR.
image_bgr = image[..., [3, 2, 1]]

There is also a mixChannels() function and a bunch other array manipulation utilities but most of these are redundant in OpenCV Python since images are backed by NumPy arrays so it's easier to just use the NumPy counterparts instead.

OpenCV uses BGR for seemingly historical reasons: Why OpenCV Using BGR Colour Space Instead of RGB.


References:

[1] OpenCV: Mat - The Basic Image Container (Search for 'BGR' under Storing methods.)

[2] OpenCV: How to scan images, lookup tables and time measurement with OpenCV

Pixel layout in BGR format. Image from [2] showing BGR layout in memory.

eugenhu
  • 1,168
  • 13
  • 22
  • I tried using `image_bgr = image[..., [3, 2, 1]]` but now it's like a blue filter applied to the actual image. – Thinkal VB Oct 18 '21 at 09:06
  • @ThinkalVB Hmm that's weird, your input image is definitely ARGB and not RGBA? Seems like this would happen if it was RGBA. – eugenhu Oct 18 '21 at 09:08
  • 1
    If it's RGBA then you'd need `image[..., [2, 1, 0]]`, or use `cvtColor()` with `cv2.COLOR_RGBA2BGR`. – eugenhu Oct 18 '21 at 09:16
  • `image[..., [2, 1, 0]]` worked - the unity 3D document says the image format is ARGB32 :-( – Thinkal VB Oct 18 '21 at 09:25
  • 1
    @ThinkalVB I'm not too familiar with Unity sorry, possibly there's something else going on when it's sending out the images and you're receiving it. If it's webcam video, I'm assuming the alpha channel will just be fixed at a constant maximum (255) value so you can use this as a check for which channel is the alpha channel (first or last index). If unity is doing some weird behaviour you can try asking a new question specifically about that and see if other people have a better idea. – eugenhu Oct 18 '21 at 09:37
  • 1
    @ThinkalVB next time, you can dump the image to npy file by [numpy.save](https://numpy.org/doc/stable/reference/generated/numpy.save.html) then upload it with your question. It will be easier for us to help and explain for you. – CuCaRot Oct 19 '21 at 04:44
0
IMAGE_WIDTH = 640
IMAGE_HEIGHT = 480
IMAGE_SIZE = IMAGE_HEIGHT * IMAGE_WIDTH * 4

try:
    while(True):
        data = sock.recv(IMAGE_SIZE)
        dataLen = len(data)
        if(dataLen == IMAGE_SIZE):
            image = numpy.fromstring(data, numpy.uint8).reshape(IMAGE_HEIGHT, IMAGE_WIDTH, 4)
            imageDisp = cv2.cvtColor(image, cv2.COLOR_RGBA2BGR)
            cv2.imshow('Image', imageDisp)
            key = cv2.waitKey(1) & 0xFF
            if key == ord("q"):
                break
finally:
    sock.close()

Edited as per the suggestions from comment

Thinkal VB
  • 189
  • 3
  • 12
  • 2
    You could improve your code by making variables for image height and width, rather than repeating 640 and 480 at different places - as that makes a nightmare for maintenance. Also, your `np.reshape()` is going to fail before your `if dataLen==IMAGE_SIZE` if the size is wrong, so your `if` statement belongs sooner in your code. – Mark Setchell Oct 18 '21 at 08:28