Vue de la caméra HAR

Les organismes de réglementation gouvernementaux mettent en œuvre plusieurs exigences pour s'assurer que la visibilité arrière indirecte fournit suffisamment d'informations pour manœuvrer le véhicule de manière précise et rapide. Cela influe sur la perception de l'environnement par le conducteur.

Pour les systèmes de visibilité arrière basés sur le système de surveillance par caméra (CMS, Camera Monitoring System), la National Highway Traffic Safety Administration (NHTSA) exige que vous respectiez les exigences suivantes (S6.6.2.3, référence UNECE46) :

  • S5.5.3 Temps de réponse L'image de rétroviseur répondant aux exigences des sections S5.5.1 (champ de vision) et S5.5.2 (taille), lorsqu'elle est testée conformément à la section S14.2, s'affiche dans les deux secondes suivant le début d'un événement de recul.

  • S5.5.4 Temps de persistance L'image de rétroviseur répondant aux exigences des sections S5.5.1 et S5.5.2 ne s'affiche pas une fois l'événement de recul terminé.

  • S5.5.5 Désactivation L'image de rétroviseur répondant aux exigences des sections S5.5.1 et S5.5.2 reste visible pendant l'événement de recul jusqu'à ce que le conducteur modifie la vue ou que le sélecteur de direction du véhicule quitte la position de marche arrière.

  • S6.6.2.3.3.5 Artefacts Le manuel de l'opérateur doit faire référence aux artefacts possibles et à leur impact sur l'occlusion partielle du champ de vision et des objets, ce qui peut nécessiter une vigilance et une attention particulières de la part du conducteur.

  • S6.2.2.3.4.1 Fréquence d'images Les mouvements des objets devant la caméra sont fluides. La fréquence d'images minimale du système est d'au moins 30 Hz (équivalent à 30 FPS). Dans des conditions de faible luminosité ou lors de manœuvres à faible vitesse, la fréquence d'images minimale du système est d'au moins 15 Hz.

  • S6.2.2.3.4.2 Temps de formation de l'image Le temps de formation de l'image du moniteur est inférieur à 55 ms à une température de 22 °C ± 5 °C.

  • S6.2.2.3.4.3 Latence du système Un système de surveillance par caméra (CMS) présente une latence suffisamment courte pour afficher le paysage presque en même temps. La latence est inférieure à 200 ms à une température de 22 °C ± 5 °C.

Nous avons introduit le système de vue étendue (EVS, Extended View System) d'Android Automotive OS (AAOS) pour répondre à ces exigences sur AAOS bare metal. Nous avons introduit un service similaire pour la virtualisation sur les appareils AAOS avec le moteur de rendu à haute disponibilité (HAR, High Availability Renderer), qui démontre également la conformité à ces exigences.

Pipeline d'aperçu de la caméra

Le pipeline d'aperçu de la caméra comporte cinq étapes :

Étapes du pipeline d'aperçu de la caméra

Figure 1 : Étapes du pipeline d'aperçu de la caméra.

Le bloc de service de caméra fait référence à la plate-forme de service de caméra et à sa couche d'abstraction qui permet aux applications d'accéder aux caméras disponibles et de les utiliser. La fonction de service d'affichage visualise les données d'imagerie pour les utilisateurs. L'application met en œuvre les parcours utilisateur cibles avec le service de caméra et le service d'affichage.

Le parcours utilisateur principal de visibilité arrière est le suivant :

  1. Le conducteur place le sélecteur de direction (le levier de vitesse) en marche arrière pour déclencher un événement de recul.

  2. Le système diffuse l'événement de recul. L'application reçoit la diffusion et initialise le bloc d'entrée de la caméra (service de caméra) et le moteur de rendu (service d'affichage).

  3. Le bloc d'entrée de la caméra initialise la plate-forme de service de caméra et renvoie le descripteur de service à l'application.

  4. Le moteur de rendu initialise la fenêtre d'affichage pour l'entrée de la caméra à partir de l'étape 4.

  5. L'application demande au bloc d'entrée de la caméra de commencer à envoyer des tampons de trame et des événements.

  6. L'application met en file d'attente les tampons de trame fournis via les rappels (asynchrones). Les tampons de trame appartiennent au bloc d'entrée de la caméra. L'application ne peut donc pas les modifier.

  7. L'application supprime un tampon de trame de la file d'attente (si elle n'est pas vide) et compose la vue utilisateur. Les utilisateurs peuvent en faire une copie pour modifier le contenu.

  8. L'application envoie un tampon au moteur de rendu.

  9. Le moteur de rendu dessine le contenu d'un tampon reçu sur l'écran.

  10. Si l'événement de recul est toujours en cours, passez à l'étape 7. Une fois l'événement de recul terminé, l'application demande au bloc de saisie par photo d'arrêter d'envoyer des tampons de trame et des événements après avoir masqué la vue à l'utilisateur.

  11. L'application ferme éventuellement une caméra et libère le moteur de rendu.

La figure 1 illustre le flux. Cette image utilise des éléments de l'API QNX Camera Library pour utiliser la plate-forme de service de caméra.

Parcours utilisateur principal de HAR

Figure 2 : Parcours utilisateur principal du moteur de rendu à haute disponibilité.

Le bloc d'entrée de la caméra déclare trois interfaces :

  • CameraManagerdéclare des méthodes permettant de gérer les appareils photo. Par exemple, l'application utilise cette interface pour ouvrir (réserver) un appareil photo cible.

  • CameraDevice déclare des méthodes permettant de contrôler un appareil photo, par exemple pour démarrer ou arrêter un flux de données.

  • CameraStreamListener déclare une seule méthode permettant de recevoir différents événements d'une caméra cible.

Conception

Cette section décrit la conception du système.

Expérience utilisateur

Le conducteur peut prévisualiser la caméra arrière sur l'écran du combiné d'instruments lorsqu'il passe la marche arrière. L'écran arrête la prévisualisation de la caméra lorsque le conducteur quitte la marche arrière.

D'autres parcours utilisateur peuvent être activés. Par exemple, le conducteur peut prévisualiser la zone non visible dans les rétroviseurs lorsque le clignotant est activé.

Démarrer l'aperçu de la caméra

Lorsqu'elle utilise des caméras, l'application énumère et évalue les caméras disponibles pour trouver la meilleure caméra pour l'objectif visé. Par exemple, pour la visibilité arrière, l'application recherche la caméra qui affiche l'arrière du véhicule dans la liste des caméras disponibles.

L'application évalue cela en examinant les caractéristiques de chaque caméra, par exemple l'emplacement, la direction de l'objectif, la fréquence d'images, la résolution de sortie et le format de sortie. Si plusieurs caméras présentent les mêmes caractéristiques requises, l'application peut examiner des caractéristiques supplémentaires, telles que le champ de vision et la distance focale.

Cette image montre une séquence de démarrage d'un aperçu de caméra avec une configuration de caméra statique :

Démarrer l'aperçu de la caméra avec une configuration statique

Figure 3 : Démarrer l'aperçu de la caméra avec une configuration de caméra statique.

Arrêter l'aperçu de la caméra

L'application cesse de fournir une visibilité arrière lorsque l'événement de recul se termine. Pour éviter d'afficher un écran vide ou une image fixe, l'application masque d'abord la vue à l'utilisateur, puis demande au bloc d'entrée de la caméra d'arrêter d'envoyer des événements.

Cette image montre une séquence d'arrêt d'un flux de données provenant d'un appareil photo cible :

Arrêter le flux de données de la caméra cible

Figure 4 : Arrêter le flux de données provenant de l'appareil photo cible.

Erreurs

Un appareil photo peut cesser d'envoyer un nouveau tampon de trame de manière inattendue. Pour détecter de tels incidents, le bloc d'entrée de la caméra peut implémenter un minuteur qui expire à l'arrivée d'une nouvelle trame et envoyer une notification lorsque ce minuteur expire.

Lorsque l'application reçoit une notification, elle informe l'utilisateur qu'un aperçu de la caméra n'est plus en direct et tente de restaurer un aperçu de la caméra en fermant un appareil photo et en le rouvrant. La figure 5 montre comment l'application gère un délai d'inactivité :

Gérer un délai avant expiration

Figure 5 : Gestion d'un délai d'inactivité (flux de données bloqué).

Le bloc d'entrée de la caméra peut signaler des incidents autres qu'un flux de données bloqué et intégrer plus de détails dans les tampons. Les OEM peuvent utiliser ces métadonnées d'événement pour gérer les incidents sur leur plate-forme.

Activités

L'API est utilisée par les applications qui s'exécutent sur l'hôte et gèrent l'écran du combiné d'instruments via le HAR (blocs bleus dans le diagramme ci-dessous).

La figure 5 illustre un schéma du système :

Diagramme du système

Figure 6 : Schéma du système.

Services

Les appels d'API doivent s'exécuter dans le contexte du processus appelant.

API

La nouvelle API est destinée uniquement aux applications qui gèrent les aperçus de la caméra sur l'écran du combiné d'instruments via le moteur de rendu à haute disponibilité. L'API est disponible via la couche d'abstraction de la plate-forme et les liens de manière dynamique.

L'interface CameraInputBlock déclare des méthodes permettant d'initialiser la fonctionnalité de la caméra et d'obtenir le gestionnaire de blocs d'entrée. L'application utilise une instance CameraManager renvoyée pour gérer les appareils photo.

// This class represents a camera input block for the application that manages the
// instrument cluster display with Harry.
public class CameraInputBlock : public InputBlock {
    public:
        // Clean up the resources.
        virtual ~CameraInputBlock();

        // A method inherited from InputBlock class. This method initializes
        // CameraInputBlock instance; e.g. checking the platform camera service
        // is live.
        //
        // @return CAMERA_EPERM if the platform camera service is not
        //                      available.
        //         CAMERA_OK otherwise.
        virtual CameraError init() override;

        // A method inherited from InputBlock class. This method release all
        // resources held by this CameraInputBlock instance.
        virtual void release() override;

        // This method returns a CameraManager instance. The caller uses
        // this instance to manage camera devices.
        //
        // @param out If this method is successful, this points to a valid
        //            CameraManager instance.
        // @return CAMERA_EACCESS when we fail to create CameraManager instance
        //         to return.
        //         CAMERA_OK otherwise.
        virtual CameraError getCameraManager(
            std::shared_ptr<CameraManager>* out) = 0;

    private:
        // Handle to manage camera devices.
        std::shared_ptr<CameraManager> mMgr;

        // Handle to manage camera devices that have been opened by clients.
        std::set<CameraDevice> mCameras;
};

La classe CameraManager déclare des méthodes permettant d'ouvrir (ou de posséder) des caméras et de les libérer lorsque l'application a terminé de les utiliser. L'application peut ouvrir plusieurs caméras et utiliser leurs flux pour créer un champ de vision plus large ou une expérience multivue.

// This pure virtual class declares methods to manage camera devices.
public class CameraManager {
    public:
        // This method returns a list of CameraDescriptor objects representing
        // available cameras.
        //
        // @param out A list of CameraDescriptor instances. This list may be
        //            empty if the platform camera service does not list any
        //            camera.
        // @return CAMERA_EACCESS if we failed to build a camera list.
        //         CAMERA_OK otherwise.
        virtual CameraError getCameraList(
            std::vector<CameraDescriptor>* out) = 0;

        // Open a camera device associated with a given string identifier.
        //
        // @param ID A string identifier of a camera device to request.
        // @param out A pointer to CameraDevice shared pointer object. This
        //            is null when we fail to open a target device.
        // @return CAMERA_ENODEV if no camera is associated with a given id.
        //         CAMERA_EACCESS if it fails to open a target device.
        //         CAMERA_OK otherwise.
        virtual CameraError open(
            std::string ID, std::shared_ptr<CameraDevice>* out) = 0;

        // Close a camera device associated with a given string identifier.
        // This method is assumed to be always successful.
        //
        // @param id A string identifier of a camera device to close.
        virtual void close(std::string id) = 0;
};

Si les applications ne parviennent pas à détecter les caméras à utiliser, elles peuvent choisir celle qui fonctionne le mieux dans le contexte. CameraManager::getCameraList() renvoie une liste d'instances CameraDescriptor, qui fournit les caractéristiques de chaque caméra.

La classe CameraDevice représente un seul appareil photo et déclare des méthodes permettant de démarrer et d'arrêter son flux de données. Si les caractéristiques de la caméra ne sont pas connues de manière statique, les clients les obtiennent à partir de leur descripteur et les analysent.

Par exemple, un client peut obtenir la liste des configurations de flux qu'un appareil photo cible propose à partir de ses métadonnées et choisir celle qui présente les meilleurs attributs (par exemple, fréquence d'images, résolution et format de sortie).

// This class represents a single camera device.
public class CameraDevice {
    public:
        // Start a data stream that attributes are matching to given
        // configuration best.
        // If a selected configuration is not given (null), a data stream is
        // initiated in its default configuration and return.
        //
        // @param configuration Selected attributes of the imagery data stream.
        // @param listener An object to listen to an active data stream.
        // @param effective Actual attributes of started data stream.
        // @return CAMERA_EINVAL if a listener object is invalid.
        //         CAMERA_EIO if we failed to start a video stream.
        //         CAMERA_OK otherwise.
        virtual CameraError start(
                std::shared_ptr<CameraStreamConfiguration>& configuration,
                std::shared_ptr<CameraStreamListener>& listener,
                std::shared_ptr<CameraStreamConfiguration>* effective) = 0;

        // Stop a data stream.
        virtual void stop() = 0;

        // Get a camera descriptor.
        //
        // @param desc A set of attributes that defines this camera device.
        // @return CAMERA_ENODATA if a descriptor is not available.
        //         CAMERA_OK otherwise.
        CameraError getDescriptor(std::shared_ptr<CameraDescriptor>* desc) = 0;

        // Return a consumed buffer to the camera device. A client of active
        // stream must return a frame buffer explicitly by calling this method.
        virtual void doneWithFrame(std::shared_ptr<FrameBuffer>& buffer) = 0;

    private:
        // Describe this camera device.
        CameraDescriptor mDescriptor;

        // A weak reference to a listening client.
        std::weak_ptr<CameraStreamListener> mClient;
};

// This class declares attributes that characterize a camera device.
public class CameraDescriptor {
    public:
        // Unique std::string object to identify a single camera device.
        std::string mId;

        // A set of stream configurations this camera device is capable of. A
        // camera must have at least one stream configuration.
        std::set<CameraStreamConfiguration> mConfigurations;

        // Are more attributes needed to exist, such as locations, lens
        // facing directions, and intrinsic/extrinsic parameters?
};

// This class declares attributes that characterize an imagery data stream.
public class CameraStreamConfiguration {
    public:
        // Width of output of this stream in pixels.
        unsigned int mWidthInPixels;

        // Height of output of this stream in pixels.
        unsigned int mHeightInPixels;

        // An average number of frames per second.
        double mFrameRate;

        // A format of this stream's output. A client could calculate a
        // byte-per-pixel (bpp) from this.
        CameraColorFormat mFormat;
};

// This class represents a listener/callback object to listen to frames and
// events.
public class CameraStreamListener {
    public:
        // A listener method to receive various stream events including a new
        // frame buffer.
        //
        // @param event CameraStreamEvent object that represents a single event
        //              such as an arrival of a new frame buffer, camera stream
        //              is terminated, and so forth.
        virtual void onEvent(std::shared_ptr<CameraStreamEvent>* event) = 0;
};

CameraDevice::start() accepte trois arguments :

  • Configuration de flux choisie par l'appelant.

  • Écouteur pour recevoir les événements de flux.

  • Pointeur vers une configuration de flux efficace. Nous recommandons vivement à l'appelant d'examiner cette valeur pour gérer les tampons de trame à venir comme prévu.

Lorsque CameraDevice::start() démarre un flux de données avec la plate-forme de service de caméra, il conserve une référence faible à l'objet d'écouteur de l'appelant pour détecter l'arrêt inattendu de l'appelant.

Lorsqu'un client a terminé d'utiliser un tampon de trame, il doit informer un appareil photo qu'il n'a plus besoin du tampon de trame en appelant la méthode CameraDevice::doneWithFrame().

Lorsqu'un flux démarre, un client reçoit des messages d'événement. Un message courant est un nouveau tampon de trame. Par le biais d'une fonction de rappel enregistrée, un client reçoit un événement kNewFrameBuffer qui contient les données d'imagerie ainsi que les métadonnées du tampon de trame. StreamEventType déclare d'autres types pour gérer d'autres événements de flux, tels que le flux de données arrêté ou bloqué.

// This class lists events possibly occurring while a data stream is active.
enum class CameraStreamEventType {
    // A delivery of a new frame buffer.
    kNewFrameBuffer,
    // A data stream has been stopped.
    kStreamStopped,
    // No new frame buffer arrives for a while.
    kStreamHang,
    // Add more.
    ...
};

// This class represents a single instance of StreamEventType.
public class CameraStreamEvent {
    public:
        // Return a type of this event.
        //
        // @return CameraStreamEventType enum value.
        CameraStreamEventType getType() { return mType; }

        // Return a pointer to data associated with this event.
        //
        // @return A shared pointer object of the buffer that contains data for
        //         this event.
        std::shared_ptr<void> getData() { return mData; }

    private:
        // Describe a type of this event.
        CameraStreamEventType mType;

        // A pointer to the data buffer.
        std::shared_ptr<void> mData;
};

// This class inherits StreamEvent class and has additional fields to represent
// the frame buffer.
public class FrameBufferEvent : public CameraStreamEvent {
    public:
        // Return an identifier of this frame buffer.
        //
        // @return A unique integer value to identify this frame buffer.
        int getBufferID() { return mBufferID; }

        // Give access to frame buffer metadata.
        //
        // @return A shared pointer to the buffer that contains data besides
        //         the imagery data.
        std::shared_ptr<void> getMetadata() { return mMetadata; }

    private:
        // Unique integer to identify this buffer.
        int mBufferID;

        // A pointer to metadata of this frame buffer.
        std::shared_ptr<void> mMetadata;
};

Cet exemple montre une implémentation de l'interface CameraInputBlock et de son application :

CameraError getCameraManager(std::shared_ptr<CameraManager>* out) {
    // During an instantiation, CameraManager will retrieve a list of camera
    // devices from the platform camera service and identify their attributes.
    *out = std::make_shared<CameraManager>();
    return CAMERA_OK;
}

// This method returns a list of CameraDescriptor objects representing available
// cameras.
CameraError CameraManager::getCameraList(std::vector<CameraDescriptor>* out) {
    if (mCameraList.size() < 1) {
        // Query a list of cameras and get their attributes.
    }
    *out = mCameraList;
    return CAMERA_OK;
}

// Open a camera device associated with a given string identifier.
CameraError CameraManager::open(std::string id, std::shared_ptr<CameraDevice>* out) {
    if (!mCameraList.contains(id)) {
        // We cannot identify any camera with a given value.
        return CAMERA_NODEV;
    }

    // During a construction, CameraDevice will obtain a handle of a target
    // camera device from the platform camera service.
    std::shared_ptr<CameraDevice> h = std::make_shared<CameraDevice>(id);
    if (!h) {
        // We fail to open a camera device.
        return CAMERA_EACCESS;
    }

    *out = h;
    return CAMERA_OK;
}

// Close a camera device associated with a given string identifier. This method
// is assumed to be always successful.
void CameraManager::close(std::string id) {
    if (!mCameraList.contains(id)) {
        // We ignore calls with unknown identifiers.
        return;
    }

    // mCameraList.remove() returns an object removed from the list.
    std::shared_ptr<CameraDevice> device = mCameraList.remove(id);

    // Ensure a device stops streaming.
    device->stop();
}

// Start a data stream that attributes are matching to given configuration
// best.
// If a selected configuration is not given (null), a data stream will be
// initiated in its default configuration and return.
CameraError CameraDevice::start(
        std::shared_ptr<CameraStreamConfiguration>& configuration,
        std::shared_ptr<CameraStreamListener>& listener,
        std::shared_ptr<CameraStreamConfiguration>* effective) {
    if (!listener) {
        return CAMERA_EINVAL;
    }

    // selectStreamConfiguration examines this camera's stream configurations
    // and returns the one closest to the selected configuration.
    CameraStreamConfiguration config = selectStreamConfiguration(configuration);

    // mDevice refers to the camera handle for the platform camera service. We
    // may need to translate CameraStreamConfiguration for the platform service.
    mDevice->configure(
        configuration.mWidth, configuration.mHeight, configuration.mFormat);

    // Start a data stream with a callback object.
    if (!mDevice->startStream(mCallback)) {
        // We failed to start a data stream.
        return CAMERA_EIO;
    }

    return CAMERA_OK;
}

// Stop a data stream.
void CameraDevice::stop() {
    if (!mDevice) {
        // Nothing to do if we don't have a valid camera handle for the
        // platform camera service.
        return;
    }

    mDevice->stopStream();
}

// Get a camera descriptor.
CameraError CameraDevice::getDescriptor(std::shared_ptr<CameraDescriptor>* desc) {
    if (!mDescriptor) {
        return CAMERA_ENODATA;
    }

    *desc = *mDescriptor;
    return CAMERA_OK;
}

// Return a consumed buffer to the camera device. A client of active stream
// must return a frame buffer explicitly by calling this method.
void CameraDevice::doneWithFrame(std::shared_ptr<FrameBuffer>& buffer) {
    if (!mBufferRecords.contains(buffer.getId())) {
        // Ignore a call with unknown frame buffer.
        return;
    }

    // Simply remove from the record.
    (void)mBufferRecords.remove(buffer.getId());
}

// This method handles gear-shift events.
void Application::handleGearShift(GearSelection selection) {
    switch (selection) {
        case GEAR_SELECTION_REVERSE:
            // Upon the reverse gear selection, we are going to start a video
            // stream and show its preview on the instrument cluster display.
            (void)startStream(mCameraInputBlock);

            // FIXME: Exact method to control the camera preview window on the
            // instrument display is to be determined.
            show(mRearVisibilityWindow);
            break;

        default:
            // Upon all other gear selection, we are going to stop a video
            // stream (if it's running) and hide the preview.
            stopStream(mCameraInputBlock);

            // FIXME: Exact method to control the camera preview window on the
            // instrument display is to be determined.
            hide(mRearVisibilityWindow);
            break;
    }
}

bool Application::startStream(std::shared_ptr<CameraInputBlock> handle) {
    return handle->start(std::bind(&Application::handleStreamCallback, this);
}

void Application::stopStream(std::shared_ptr<CameraInputBlock> handle) {
    handle->stop();
}

// This method handles a stream callback.
void Application::handleStreamCallback(StreamEvent& event) {
    switch (event.getType()) {
        case StreamEventType::kNewFrameBuffer:
            // Handle a new frame buffer. We may just enqueue it for the
            // future or forward to CameraInputBlock client.
            break;

        case StreamEventType::kStreamStopped:
            // Handle as an incident if this event is not expected.
            break;

        // More cases to be added.
    }
}

void Application::handleNewFrameBuffer(StreamEvent& event) {
    // Enqueue a new frame buffer for the further processing. A buffer
    // must be returned explicitly by calling
    // CameraDevice.doneWithFrame(FrameBuffer&) method.
}

void Application::handleStreamEvent(StreamEvent& event) {
    // Handle a received stream event except a new frame buffer's
    // arrival; e.g. a video stream is terminated unexpectedly.
}

Performances

La visibilité arrière est conforme à ces réglementations gouvernementales.

Valeur Réglementation
Temps de réponse CFR 571.111 S5.5.3
Fréquence d'images UNECE R46 6.2.2.3.4
Temps de formation de l'image UNECE R46 6.2.2.3.4.2
Latence du système UNECE R46 6.2.2.3.4.3

Confidentialité

Spécificités concernant la confidentialité :

  • L'API n'exige pas que les implémentations collectent, enregistrent ou stockent des informations personnelles. Toutefois, étant donné que les données d'imagerie capturées (ou les métadonnées associées) peuvent contenir des informations personnelles, l'application qui utilise l'API doit obtenir le consentement explicite de l'utilisateur.

  • Les utilisateurs ne peuvent pas contrôler les appareils photo pour prévisualiser l'écran du combiné d'instruments, car les caméras jouent un rôle essentiel en matière de sécurité. Les OEM obtiennent le consentement utilisateur lors de la configuration ou auprès du conducteur.

  • Cette API n'est pas compatible avec les clients de caméra en arrière-plan. Par conséquent, l'indicateur de confidentialité, qui informe les utilisateurs qu'un appareil photo capture des données, n'est pas pris en compte.