WebCamTexture, get correct resolution and RATIO.

After searching for a while, I’ve seen different solutions to get this right. Most of them suggesting first instantiating the WebCamTexture to a high resoulution. This does not work at all.

How do you, correctly, get the actual camera aspect ratio and the actual pixels??

Update:

Here’s the full script I’m using for displaying the device camera feed on a UI image with correct orientation and ratio, front and back cameras, and mirroring the front camera to make it look more natural, along with a screenshot of my hierarchy and RawImage game object.
It’s based on this answer by @Fattie, this other answer by Max Bot, reading the Unity documentation on WebCamTexture and WebCamDevice, and trial and error on a Nexus 5 running Android 6 and an iPhone 5S running iOS 9.

Hierarchy:

DeviceCameraController.cs:

using UnityEngine;
using UnityEngine.UI;
using System.Linq;
using System.Collections;

public class DeviceCameraController : MonoBehaviour
{
    public RawImage image;
    public RectTransform imageParent;
    public AspectRatioFitter imageFitter;

    // Device cameras
    WebCamDevice frontCameraDevice;
    WebCamDevice backCameraDevice;
    WebCamDevice activeCameraDevice;

    WebCamTexture frontCameraTexture;
    WebCamTexture backCameraTexture;
    WebCamTexture activeCameraTexture;

    // Image rotation
    Vector3 rotationVector = new Vector3(0f, 0f, 0f);

    // Image uvRect
    Rect defaultRect = new Rect(0f, 0f, 1f, 1f);
    Rect fixedRect = new Rect(0f, 1f, 1f, -1f);

    // Image Parent's scale
    Vector3 defaultScale = new Vector3(1f, 1f, 1f);
    Vector3 fixedScale = new Vector3(-1f, 1f, 1f);


    void Start()
    {
        // Check for device cameras
        if (WebCamTexture.devices.Length == 0)
        {
            Debug.Log("No devices cameras found");
            return;
        }

        // Get the device's cameras and create WebCamTextures with them
        frontCameraDevice = WebCamTexture.devices.Last();
        backCameraDevice = WebCamTexture.devices.First();

        frontCameraTexture = new WebCamTexture(frontCameraDevice.name);
        backCameraTexture = new WebCamTexture(backCameraDevice.name);

        // Set camera filter modes for a smoother looking image
        frontCameraTexture.filterMode = FilterMode.Trilinear;
        backCameraTexture.filterMode = FilterMode.Trilinear;

        // Set the camera to use by default
        SetActiveCamera(frontCameraTexture);
    }

    // Set the device camera to use and start it
    public void SetActiveCamera(WebCamTexture cameraToUse)
    {
        if (activeCameraTexture != null)
        {
            activeCameraTexture.Stop();
        }
            
        activeCameraTexture = cameraToUse;
        activeCameraDevice = WebCamTexture.devices.FirstOrDefault(device => 
            device.name == cameraToUse.deviceName);

        image.texture = activeCameraTexture;
        image.material.mainTexture = activeCameraTexture;

        activeCameraTexture.Play();
    }

    // Switch between the device's front and back camera
    public void SwitchCamera()
    {
        SetActiveCamera(activeCameraTexture.Equals(frontCameraTexture) ? 
            backCameraTexture : frontCameraTexture);
    }
        
    // Make adjustments to image every frame to be safe, since Unity isn't 
    // guaranteed to report correct data as soon as device camera is started
    void Update()
    {
        // Skip making adjustment for incorrect camera data
        if (activeCameraTexture.width < 100)
        {
            Debug.Log("Still waiting another frame for correct info...");
            return;
        }

        // Rotate image to show correct orientation 
        rotationVector.z = -activeCameraTexture.videoRotationAngle;
        image.rectTransform.localEulerAngles = rotationVector;

        // Set AspectRatioFitter's ratio
        float videoRatio = 
            (float)activeCameraTexture.width / (float)activeCameraTexture.height;
        imageFitter.aspectRatio = videoRatio;

        // Unflip if vertically flipped
        image.uvRect = 
            activeCameraTexture.videoVerticallyMirrored ? fixedRect : defaultRect;

        // Mirror front-facing camera's image horizontally to look more natural
        imageParent.localScale = 
            activeCameraDevice.isFrontFacing ? fixedScale : defaultScale;
    }
}

Comments on Fattie’s Answer:

So I’m pretty sure at this point that “verticallyMirrored” means that a rotation about the horizontal axis is needed. So I’ve modified the code in Fattie’s answer to:

   int ccwNeeded = -wct.videoRotationAngle;
   
   rawImageRT.localEulerAngles = new Vector3(0f,0f,ccwNeeded);
   
   float videoRatio = (float)wct.width/(float)wct.height;
   
   rawImageARF.aspectRatio = videoRatio;
   
   if ( wct.videoVerticallyMirrored )
     rawImage.uvRect = new Rect(0,1,1,-1);  // flip on HORIZONTAL axis
   else
     rawImage.uvRect = new Rect(0,0,1,1); // no flip

i.e. I removed “if ( wct.videoVerticallyMirrored ) ccwNeeded += 180;” and changed the flipped uvRect values. I’ll post a new answer here or somewhere on this site with my full script once I iron it out which includes using both front and back cameras and mirroring the front camera’s texture to make it more natural.

#note

Thes days just use

#NatCam

on the asset store. It saves 4-10 man-weeks of work.


Jonny Roy’s answer is partially correct. This is a huge know bug / disaster in Unity. For years they have not fixed, mentioned or addressed the issue.

#(1) The solution is this: the reported size only becomes correct after some time (1/2 sec or so).

#(2) Before that the width is reported as a small number under 100. it sounds bizarre but you have to watch each frame for a number over 100. It works 100% reliably.

Note these days you would never use a WebCamTexture on anything but UI, here is a post to save some typing: Getting A Web Cam to Play on UI Texture Image - Unity Answers

And nowm how to spin the image!

#Here is the state of the art, 2016, for fixing the ratio, spin, and mirror of WebCamTexture. Works on both Android and iOS:

private void Update()
  {
  if ( wct.width < 100 )
    {
    Debug.Log("Still waiting another frame for correct info...");
    return;
    }
  
  // change as user rotates iPhone or Android:
  
  int cwNeeded = wct.videoRotationAngle;
  // Unity helpfully returns the _clockwise_ twist needed
  // guess nobody at Unity noticed their product works in counterclockwise:
  int ccwNeeded = -cwNeeded;
  
  // IF the image needs to be mirrored, it seems that it
  // ALSO needs to be spun. Strange: but true.
  if ( wct.videoVerticallyMirrored ) ccwNeeded += 180;
  
  // you'll be using a UI RawImage, so simply spin the RectTransform
  rawImageRT.localEulerAngles = new Vector3(0f,0f,ccwNeeded);
  
  float videoRatio = (float)wct.width/(float)wct.height;
  
  // you'll be using an AspectRatioFitter on the Image, so simply set it
  rawImageARF.aspectRatio = videoRatio;
  
  // alert, the ONLY way to mirror a RAW image, is, the uvRect.
  // changing the scale is completely broken.
  if ( wct.videoVerticallyMirrored )
    rawImage.uvRect = new Rect(1,0,-1,1);  // means flip on vertical axis
  else
    rawImage.uvRect = new Rect(0,0,1,1);  // means no flip
  
  // devText.text =
  //  videoRotationAngle+"/"+ratio+"/"+wct.videoVerticallyMirrored;
  }

Hope it saves someone a LOT of time.

#However see George’s critical notes in the other answer!!!

Have you tried NatCam? It works wonders and has a lot more features.

Unity 5.6.1 Release Notes [Extract]:

  • Android: Fixed WebCamTexture crash with denied permissions. (877837)

  • Android: Webcam - Fixed the wrong orientation returned on first frames. (875247)

Hope this solves a small issue faced here.

Hey I was having good fun with this on iOS, but I resolved it in the end with something pretty short and sweet.


I still needed to wait for the WebCamTexture.width to be more than 100 before adding it to the raw image texture/material and then I used SetNativeSize() on the raw texture. Here is my code for initialization:

        private IEnumerator WaitForWebCamAndInitialize( WebCamTexture _webCamTexture ) 
        {
            while( _webCamTexture.width < 100 )
                yield return null;

            _isWebCamSet = true;
            _rawCamImage.texture = _webCamTexture;
            _rawCamImage.material.mainTexture = _webCamTexture;
            _rawCamImage.SetNativeSize();

            StartCoroutine( FadeToCameraView() );
        }

FadeToCameraView() does as it suggests.


I also had to rotate the rect transform negative 90 on the z axis because the app runs in portrait mode only and the raw data from the iOS camera seems to be aligned with landscape mode. Now everything works like a charm on iOS, but testing on the iMac shows the image rotated by the negative 90 I gave it.


Hope that helps someone