Main Content

Create an App for Live Image Acquisition

This example shows how to build an app in App Designer that discovers all available cameras and shows a custom live preview from the selected camera. Additionally, the example shows how to use the app to configure camera properties, set up and configure a preview, and save images and video to disk.

What you need to run the app:

  • Image Acquisition Toolbox™

  • One of the following adaptors: winvideo, macvideo, linuxvideo (Image Acquisition Toolbox Support Package for OS Generic Video Interface), gentl (Image Acquisition Toolbox Support Package for GenICam™ Interface), gige (Image Acquisition Toolbox Support Package for GigE Vision Hardware). Other adaptors might work with minor modifications.

  • A camera supported by one of the installed adaptors

Design the App Layout

Lay out the app in App Designer using Design View to provide controls for users based on the app goals. In this case, lay out the app so that users can:

  • Create and maintain the list of available cameras.

  • Connect and disconnect from cameras.

  • Set camera properties.

  • Get a live preview and configure how it is displayed.

To organize these functions, the app layout contains different panels, such as Camera Connection, Camera Properties, Preview Properties, Logging, and a live preview display.

Create and Maintain Camera List

Create an app helper function, refreshCameraList, to get the list of connected cameras and populate the camera selection drop-down items.

function refreshCameraList(app)
    updateDeviceTable(app);
    deviceList = strcat(app.DeviceTable(:,:).Name, '-', app.DeviceTable(:,:).Adaptor);
    app.CameraDropDown.Items = deviceList;
    app.FormatDropDown.Items = imaqhwinfo(app.DeviceTable(1,:).Adaptor{1}).DeviceInfo.SupportedFormats;
end

To maintain the camera list, the updateDeviceTable helper function uses the imaqhwinfo function and steps through the installed adaptors to get all associated devices. The function saves camera information in a table containing the adaptor name, the device index within that adaptor, and the name of the device.

function updateDeviceTable(app)
    % Create the device table with all recognized devices
    app.DeviceTable = table;

    % Get a list of all the installed adaptors
    adaptors = imaqhwinfo().InstalledAdaptors;

    % Iterate over every element in an array
    for adaptor = adaptors % Step through each adaptor
        devices = imaqhwinfo(cell2mat(adaptor)).DeviceInfo; % get all devices for adaptor
        for ii = 1:size(devices,2)
            % Add the device to the device table
            app.DeviceTable = [app.DeviceTable; {string(adaptor), ii, string(devices(ii).DeviceName)}];
        end
    end

    % Set the table column names for easy access in the future
    app.DeviceTable.Properties.VariableNames = ["Adaptor", "Number", "Name"];
end

When the app starts, the startupFcn executes refreshCameraList to update the camera list in the app.

function startupFcn(app)
    ...
    refreshCameraList(app)
    ...
end

Connect to Camera

When the app user clicks the Connect button create a videoinput object based on the selected camera. To allow other app functions to access the videoinput object, store it in an app property, app.VideoObject.

function ConnectButtonPushed(app, event)
    ...
    app.VideoObject = videoinput(string(selectedCamera.Adaptor), selectedCamera.Number, app.FormatDropDown.Value);
    ...
end

To disconnect from the camera, execute the videoinput delete function. Access the videoinput object using the app property.

function DisconnectButtonPushed(app, event)
    stoppreview(app.VideoObject);
    delete(app.VideoObject);
    ...
end

Configure Camera Properties

Cameras can have a large number of configurable properties. However, the app user might want to adjust only a few properties. The setupPropUIComponents helper functions programmatically generates UI components for the user specified camera properties. Specifically, it creates a slider and numeric edit field for each numeric camera property and a dropdown for each camera property that is an enumeration.

When an app user changes a UI component property value, update the camera property value in the component callback function. If the specified value is not supported by the camera, then the camera adjusts the property to a supported value instead. Refresh the UI components to keep them in sync with the updated camera property value.

The UI component Tag object identifier and the findobj function find the UI component that is associated with the camera property being updated.

function sliderChangedFcn(app, src, event)
    app.CameraSource.(src.Tag) = event.Value;
    
    actualValue = app.CameraSource.(src.Tag);
    src.Value = double(actualValue);

    editField = findobj(app.PropertiesLayout,'Tag',src.Tag + "Edit");
    editField.Value = double(actualValue);
end

Preview

Use a custom preview callback to process the captured images before they are displayed live. Also, for cameras with high resolution, use the MaxRenderedResolution image property to improve preview performance by using a lower onscreen preview resolution.

function refreshPreview(app)
    ...
    app.ImagePreview = image(app.imageAxes,zeros(resolution(2), resolution(1), nBands, imaqinfo.NativeDataType));
    ...
    app.ImagePreview.MaxRenderedResolution = 1920;
    ...
    setappdata(app.ImagePreview,'UpdatePreviewWindowFcn',@(obj,event,hImage) previewCallback(app,obj,event,hImage))
    ...
    preview(app.VideoObject,app.ImagePreview);
end

This example uses the fliplr function to mirror the image

function previewCallback(app,obj,event,hImage)
    cdata = event.Data;
    if app.MirrorSwitch.Value == "On"
        cdata = fliplr(cdata);
    end
    hImage.CData = cdata;
end

Non-optical cameras can return data in monochrome format (intensity images). For example, thermal infrared cameras return temperature image data. For this type of application, to effectively visualize the data, apply a non-grayscale colormap and set color limits. Additionally, when using a format with a bitdepth of greater than 8 bits, the PreviewFullBitDepth property needs to be set so that the entire data range can be visualized appropriately.

function adjustPreviewBands(app)
    nBands = app.VideoObject.NumberOfBands;

    % Check for grayscale/scientific formats
    if(nBands == 1)
        app.VideoObject.PreviewFullBitDepth = "on";
        ...
        app.ImagePreview.CDataMapping = 'scaled';
        app.ImagePreview.Parent.CLimMode = 'auto';
        ...
    end
end
function ColormapDropDownValueChanged(app, event)
    value = app.ColormapDropDown.Value;
    colormap(app.ImagePreview.Parent,value);
end

Save Images and Video to Disk

To save an image to disk, call videoinput getsnapshot to get an image, and use the imwrite function.

function SnapshotButtonPushed(app, event)
    img = getsnapshot(app.VideoObject);
    ...
    imwrite(img,fileName);
    ...
end

To save video to disk use the builtin videoinput DiskLogger functionality.

function RecordButtonPushed(app, event)
    ...
    logfile = VideoWriter(fileName, format);
    app.VideoObject.DiskLogger = logfile;
    ... 
    start(app.VideoObject)
end

Handling Errors

You can display errors as alerts in the app instead of in the Command Window. Use Use try/catch to Handle Errors blocks and the uialert function.

function ConnectButtonPushed(app, event)
    ...
    try
        app.VideoObject = videoinput(string(selectedCamera.Adaptor), selectedCamera.Number, app.FormatDropDown.Value);
    catch E
        uialert(app.UIFigure,E.message, "Camera Object Creation Error");
        return
    end
    ...
end

Customize and Run the App

To run this app with your camera, customize the camera properties that you want to configure when using the app.

You can specify properties in the CameraProperties structure, which is defined as an app property. The available properties depend on the videoinput adaptor and your camera. This example app supports both numeric and enumerated properties.

properties (Access = private)
    ...
    CameraProperties = struct( ...
        winvideo = ["Exposure", "ExposureMode", "FrameRate"], ...
        gentl = ["AcquisitionFrameRate", "AcquisitionFrameRateEnable", "ExposureTime", "ExposureAuto"], ...
        gige = ["AcquisitionFrameRate", "AcquisitionFrameRateEnable", "ExposureTime", "ExposureAuto"], ...
        demo = string.empty, ...
        macvideo = string.empty, ...
        linuxvideo = string.empty)
    ...
end

Some camera properties are interdependent, for example, Exposure and ExposureMode. You can define the dependent properties in the CameraPropertiesDict app property, which is a structure with dictionary fields for each videoinput adaptor. The dictionary key is the controlling property and the value is the dependent property.

properties (Access = private)
    ...
    CameraPropertiesDict = struct( ...
        winvideo=dictionary(ExposureMode="Exposure"), ...
        gentl=dictionary( ...
            AcquisitionFrameRateEnable="AcquisitionFrameRate", ...
            ExposureAuto="ExposureTime"), ...
        gige=dictionary( ...
            AcquisitionFrameRateEnable="AcquisitionFrameRate", ...
            ExposureAuto="ExposureTime"), ...
        demo = dictionary(), ...
        macvideo = dictionary(), ...
        linuxvideo = dictionary())
    ...
end

The app property DisablePropStrings defines which value controls the state (enabled/disabled) of the UI components associated with the paired dependent property. For example, Exposure and ExposureMode are paired. To enable the Exposure slider and numeric edit field, ExposureMode must be set to manual.

properties (Access = private)
    ...
    DisablePropStrings = struct( ...
        winvideo="manual", ...
        gentl=["True", "Off"], ...
        gige=["True", "Off"], ...
        demo = [], ...
        macvideo = [], ...
        linuxvideo = [])
    ...
end

See Also