# Cameras, Preview and Image Settings in C# (AR51.Net)

List the AR 51 cameras with their **calibration values**, stream a **live JPEG preview**, read and change their **image settings**, and tune them automatically. Cameras are spread over several camera servers; `ar51.Cameras` covers all of them.

## List the cameras

```csharp
foreach (CameraInfo camera in await ar51.Cameras.GetCamerasAsync())
    Console.WriteLine($"{camera.Name}: {camera.Width}x{camera.Height} @ {camera.FrameRate} fps, " +
                      $"{(camera.IsConnected ? "connected" : "offline")}");
```

Each [`CameraInfo`](/docs/sdk-api/csharp-api/classes/camerainfo/) has the camera's id (its serial number), name, driver type, connection state, the camera server it's plugged into, image size and frame rate, and its calibration — or `null` when the camera isn't calibrated. A camera server that doesn't answer in time is left out.

## Calibration values

Calibration follows the **OpenCV convention**: focal lengths (`Fx`, `Fy`) and principal point (`Cx`, `Cy`) in pixels, lens distortion in the order k1, k2, p1, p2, k3, and `WorldToCamera`, which maps an AR 51 world point into the camera's space.

| Space | Units | Axes |
|---|---|---|
| AR 51 world | meters | left-handed: X right, Y up, Z forward |
| Camera space | meters | X right, Y down, Z forward (OpenCV) |

To project a world point to a pixel (before distortion):

```csharp
Vector3 p = Vector3.Transform(worldPoint, calibration.WorldToCamera);
float u = calibration.Fx * p.X / p.Z + calibration.Cx;
float v = calibration.Fy * p.Y / p.Z + calibration.Cy;
```

The camera's own position in the world is the translation of the inverse matrix:

```csharp
Matrix4x4.Invert(calibration.WorldToCamera, out Matrix4x4 cameraToWorld);
Console.WriteLine($"camera at {cameraToWorld.Translation} m");
```

## Live preview

`StreamPreviewAsync` streams a camera's video as JPEG images. **Ending the `await foreach` loop stops the preview.**

```csharp
var options = new PreviewOptions { FitWidth = 640, FitHeight = 480, FrameRate = 5 };
await foreach (CameraFrame frame in ar51.Cameras.StreamPreviewAsync(camera.Id, options))
{
    // frame.Jpeg holds one JPEG image; show it or save it.
}
```

The image is shrunk to fit inside `FitWidth` × `FitHeight`, keeping the camera's aspect ratio — never enlarged. `null` values use the camera's own size and frame rate; `JpegQuality` defaults to 70.

:::tip[Many previews at once]
Each preview costs network bandwidth. Open **one stream per camera** and share it between every view that shows that camera, and stream only the cameras on screen. For a wall of camera tiles, request small images at a low frame rate. The [web dashboard sample](/docs/csharp-api/web-dashboard/) does all three.
:::

## Image settings

:::caution[Settings affect every app using the system]
Settings are saved on the server and apply to **every app connected to the AR 51 system**. Excluding a camera from tracking, or changing its exposure during a live session, changes tracking for everyone.
:::

Read a camera's settings, change values with `with`, and send them back. Only the values that differ are sent, and the server **saves them**, so they survive a restart. The API doesn't report valid ranges, so **keep each setting within the camera's range**: a value outside it is clamped by the camera to the nearest limit, and current servers report the value you sent rather than the one the camera uses.

```csharp
CameraSettings settings = await ar51.Cameras.GetSettingsAsync(camera.Id);
await ar51.Cameras.SetSettingsAsync(camera.Id, settings with { ExposureMicroseconds = 4000, Gain = 2 });
```

<div className="apiTable">

| Setting | What it does |
|---|---|
| `ExposureMicroseconds` | Exposure time, in microseconds |
| `Gain`, `Gamma`, `Contrast`, `Saturation` | Overall gain, gamma correction, contrast, color saturation |
| `GainRed`, `GainGreen`, `GainBlue` | Color balance |
| `Rotate180` | Turn the image upside down, for a camera mounted upside down |
| `ExcludedFromTracking` | Leave the camera out of tracking, for example while it's faulty |

</div>

## Auto-tune

Auto-tune adjusts each camera's color balance (`GainRed`, `GainGreen`, `GainBlue`) using the camera's own one-shot white balance — for every camera you include (all of them by default), for every app using the system. It takes about two seconds. If you may want the old values back, save them first with `GetSettingsAsync`; read the new values afterwards the same way. Follow it with events:

```csharp
ar51.Cameras.AutoTuneProgressChanged += (_, e) => Console.WriteLine($"auto-tune {e.Status.Progress:P0}");
ar51.Cameras.AutoTuneEnded += (_, e) => Console.WriteLine($"auto-tune {e.Outcome}");
await ar51.Cameras.StartAutoTuneAsync();   // all cameras; pass AutoTuneOptions to choose some
```

It ends as `Completed`, `Failed` (see each camera's error) or `Cancelled`.

:::note[Current AR 51 servers]
Current servers don't report auto-tune progress, so AR51.Net estimates it: `AutoTuneStarted` at once, `AutoTuneProgressChanged` over about two seconds, then `AutoTuneEnded` with `Completed`. While it estimates, `AutoTuneStatus.IsEstimated` is `true`. Current servers also can't cancel an auto-tune: `CancelAutoTuneAsync` doesn't throw, and the auto-tune finishes as `Completed`. An auto-tune started by another app isn't seen. When a server reports real progress, AR51.Net uses it instead, with no change to your code.
:::

## Complete sample

List the cameras with their calibration, save one preview image of the first camera, and print its settings.

{/* SAMPLE:05-cameras — generated from the ar51-net-samples repo; edit there */}
```csharp title="Program.cs"
// List the cameras with their calibration, save one preview image of the first camera, and show its settings.
using AR51.Net;
using System.Numerics;

await using IAR51Client ar51 = AR51Client.Create();
await ar51.StartAsync();

IReadOnlyList<CameraInfo> cameras = await ar51.Cameras.GetCamerasAsync();
foreach (CameraInfo camera in cameras)
{
    Console.WriteLine($"{camera.Name} ({camera.Type}) {camera.Width}x{camera.Height} @ {camera.FrameRate} fps, " +
                      $"{(camera.IsConnected ? "connected" : "offline")}, on {camera.ServerHost}");
    if (camera.Calibration is { } calibration)
    {
        Matrix4x4.Invert(calibration.WorldToCamera, out Matrix4x4 cameraToWorld);
        Console.WriteLine($"    fx {calibration.Fx:F1} px, position {cameraToWorld.Translation} m");
    }
}

if (cameras.Count == 0) return;
CameraInfo first = cameras[0];

// A small preview that fits a 640 x 480 box, keeping the camera's aspect ratio. Ending the loop stops the preview.
await foreach (CameraFrame frame in ar51.Cameras.StreamPreviewAsync(first.Id, new PreviewOptions { FitWidth = 640, FitHeight = 480, FrameRate = 5 }))
{
    var file = $"{first.Id}.jpg";
    await File.WriteAllBytesAsync(file, frame.Jpeg.ToArray());
    Console.WriteLine($"Saved {file}: {frame.Width}x{frame.Height}, camera at {frame.CameraFps:F0} fps");
    break;
}

CameraSettings settings = await ar51.Cameras.GetSettingsAsync(first.Id);
Console.WriteLine($"Settings of {first.Name}: {settings}");
// To change a setting: await ar51.Cameras.SetSettingsAsync(first.Id, settings with { Gain = 2 });
```

[View this sample on Bitbucket](https://bitbucket.org/ar51/ar51-net-samples/src/main/05-cameras/) — clone the [samples repository](https://bitbucket.org/ar51/ar51-net-samples) to run it.
{/* /SAMPLE */}

## Reference

[`ICameraClient`](/docs/sdk-api/csharp-api/classes/icameraclient/) · [`CameraInfo`](/docs/sdk-api/csharp-api/classes/camerainfo/) · [`CameraCalibration`](/docs/sdk-api/csharp-api/classes/camerainfo/#cameracalibration) · [`PreviewOptions`](/docs/sdk-api/csharp-api/classes/icameraclient/#previewoptions) · [`CameraSettings`](/docs/sdk-api/csharp-api/classes/camerasettings/)
