Os reguladores governamentais implementam vários requisitos para garantir que a visibilidade traseira indireta forneça informações suficientes para manobrar o veículo de maneira precisa e oportuna. Isso influencia a percepção do motorista sobre o entorno.
Para sistemas de visibilidade traseira baseados no sistema de monitoramento por câmera (CMS, na sigla em inglês), a Administração Nacional de Segurança de Tráfego em Rodovias (NHTSA, na sigla em inglês) exige que você atenda a estes requisitos (S6.6.2.3, conforme UNECE46):
S5.5.3 Tempo de resposta. A imagem do retrovisor que atende aos requisitos de S5.5.1 (campo de visão) e S5.5.2 (tamanho), quando testada de acordo com S14.2, aparece em até 2 segundos após o início de uma manobra de ré.
S5.5.4 Tempo de permanência. A imagem da câmera traseira que atende aos requisitos de S5.5.1 e S5.5.2 não é mostrada após o fim do evento de ré.
S5.5.5 Desativação. A imagem do espelho retrovisor que atende aos requisitos de S5.5.1 e S5.5.2 permanece visível durante o evento de ré até que o motorista mude a visão ou o seletor de direção do veículo saia da posição de ré.
S6.6.2.3.3.5 Artefatos. O manual do operador deve se referir a possíveis artefatos e seus impactos na oclusão parcial do campo de visão e dos objetos, o que pode exigir que o motorista fique particularmente alerta e atento.
S6.2.2.3.4.1 Frame rate. Os movimentos dos objetos na frente da câmera são renderizados de forma suave e fluida. A taxa de atualização mínima do sistema é de pelo menos 30 Hz (equivalente a 30 fps). Em condições de pouca luz ou ao manobrar em baixa velocidade, a taxa de frames mínima do sistema é de pelo menos 15 Hz.
S6.2.2.3.4.2 Tempo de formação da imagem. O tempo de formação de imagem do monitor é inferior a 55 ms a uma temperatura de 22 graus Celsius ± 5 graus Celsius.
S6.2.2.3.4.3 Latência do sistema. Um sistema de monitoramento por câmera (CMS) tem uma latência suficientemente curta para renderizar o cenário quase ao mesmo tempo. A latência é menor que 200 ms a uma temperatura de 22 graus Celsius ± 5 graus Celsius.
Apresentamos o Sistema de visualização estendida (EVS) do Android Automotive OS (AAOS) para atender a esses requisitos no AAOS bare metal. Introduzimos um serviço semelhante para virtualização em dispositivos AAOS com o renderizador de alta disponibilidade (HAR, na sigla em inglês), que também demonstra conformidade com esses requisitos.
Pipeline de visualização da câmera
Essas cinco etapas compõem o pipeline de visualização da câmera:
Figura 1. Estágios do pipeline de visualização da câmera.
O bloqueio do serviço de câmera se refere à plataforma do serviço de câmera e à camada de abstração dela, que permite que os apps acessem e usem as câmeras disponíveis. A função "Display Service" visualiza os dados de imagens para os usuários. O app implementa as jornadas do usuário de destino com o serviço de câmera e o serviço de exibição.
A principal jornada do usuário de visibilidade traseira é:
O motorista coloca o seletor de direção (a marcha) em ré para acionar um evento de marcha a ré.
O sistema transmite o evento de suporte. O app recebe a transmissão e inicializa o bloco de entrada de câmera (serviço de câmera) e o renderizador (serviço de exibição).
O bloco de entrada da câmera inicializa a plataforma do serviço de câmera e retorna o identificador de serviço para o app.
O renderizador inicializa a janela de visualização para a entrada de câmera da etapa 4.
O app solicita o bloco de entrada da câmera para começar a enviar buffers de frames e eventos.
O app enfileira buffers de frames entregues pelos callbacks (assíncronos). Os buffers de frame são de propriedade do bloco de entrada de câmera, então o app não pode modificá-los.
O app retira um frame buffer da fila (se ela não estiver vazia) e compõe a visualização do usuário. Os usuários podem fazer uma cópia para modificar o conteúdo.
O app envia um buffer para o renderizador.
O renderizador desenha o conteúdo de um buffer recebido na tela.
Se o evento de apoio ainda estiver em andamento, vá para a etapa 7. Quando o evento de apoio é concluído, o app pede que o bloco de entrada de câmera pare de enviar buffers de frames e eventos depois de ocultar a visualização do usuário.
O app fecha uma câmera e libera o renderizador, se quiser.
A Figura 1 ilustra o fluxo. Esta imagem usa elementos da API QNX Camera Library para usar a plataforma do serviço de câmera.
Figura 2. Jornada principal do usuário do HAR.
O bloco de entrada de câmera declara três interfaces:
CameraManager, declara métodos para gerenciar dispositivos de câmera. Por exemplo, o app usa essa interface para abrir (reservar) um dispositivo de câmera de destino.CameraDevicedeclara métodos para controlar um dispositivo de câmera, por exemplo, iniciar ou interromper um fluxo de dados.CameraStreamListenerdeclara um único método para receber vários eventos de uma câmera de destino.
Design
Esta seção detalha o design do sistema.
Experiência do usuário
O motorista pode conferir a câmera traseira no painel de instrumentos ao engatar a marcha à ré. A tela para de mostrar a câmera quando o motorista tira a marcha à ré.
Outras jornadas de usuário podem ser ativadas. Por exemplo, o motorista pode ver a área não visível nos espelhos quando a seta é ativada.
Iniciar a visualização da câmera
Ao usar câmeras, o app enumera e avalia as câmeras disponíveis para encontrar a melhor opção para a finalidade pretendida. Por exemplo, para visibilidade traseira, o app procura na lista de câmeras disponíveis aquela que mostra a parte de trás do veículo.
O app avalia isso examinando as características de cada câmera, por exemplo, localização, direção da lente, taxa de frames, resolução e formato de saída. Se várias câmeras tiverem as mesmas características necessárias, o app poderá examinar outras características, como o campo de visão e a distância focal.
Esta imagem mostra uma sequência para iniciar uma visualização da câmera com uma configuração estática:
Figura 3. Inicie a visualização da câmera com uma configuração estática.
Parar a visualização da câmera
O app para de fornecer visibilidade traseira quando o evento de ré termina. Para evitar mostrar uma tela em branco ou uma imagem estática, o app primeiro oculta a visualização do usuário e depois solicita que o bloco de entrada da câmera pare de enviar eventos.
Esta imagem mostra uma sequência para interromper um fluxo de dados de um dispositivo de câmera de destino:
Figura 4. Interrompe o fluxo de dados do dispositivo de câmera de destino.
Erros
Um dispositivo de câmera pode parar de enviar um novo buffer de frames inesperadamente. Para detectar esses incidentes, o bloco de entrada da câmera pode implementar um timer que expira quando um novo frame chega e envia uma notificação quando esse timer expira.
Quando o app recebe uma notificação, ele informa ao usuário que uma visualização da câmera não está mais ativa e tenta restaurar uma visualização da câmera fechando e abrindo novamente um dispositivo de câmera. A Figura 5 mostra como o app processa um tempo limite:
Figura 5. Como lidar com um tempo limite (fluxo de dados pendente).
O bloco de entrada da câmera pode informar incidentes diferentes de um fluxo de dados travado e incorporar mais detalhes nos buffers. Os OEMs podem usar esses metadados de eventos para lidar com incidentes na plataforma deles.
Atividades
A API é usada por apps executados no host e gerencia a tela do painel de instrumentos pelo HAR (blocos azuis no diagrama abaixo).
Um diagrama do sistema é ilustrado na Figura 5:
Figura 6. Diagrama do sistema.
Serviços
As chamadas de API precisam ser executadas no contexto do processo de chamada.
APIs
A nova API é destinada apenas a apps que gerenciam as prévias da câmera na tela do painel de instrumentos pelo HAR. A API está disponível na camada de abstração da plataforma e é vinculada dinamicamente.
A interface CameraInputBlock declara métodos para inicializar a funcionalidade
da câmera e receber o gerenciador de bloqueio de entrada. O app usa uma instância CameraManager retornada para gerenciar dispositivos de câmera.
// 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;
};
A classe CameraManager declara métodos para abrir (ou possuir) câmeras e as libera quando o app termina de usar a câmera. O app pode abrir mais de uma
câmera e consumir os streams delas para criar um campo de visão mais amplo ou uma experiência
multiview.
// 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;
};
Se os apps não conseguirem detectar quais câmeras usar, eles poderão escolher a que funciona
melhor no contexto. CameraManager::getCameraList() retorna uma lista de instâncias CameraDescriptor, que fornece as características de cada câmera.
A classe CameraDevice representa um único dispositivo de câmera e declara métodos
para iniciar e interromper o fluxo de dados. Se as características da câmera não forem conhecidas de forma estática, os clientes vão recebê-las do descritor e analisá-las.
Por exemplo, um cliente pode receber uma lista de configurações de stream que um dispositivo de câmera de destino oferece nos metadados e escolher aquela com os melhores atributos (por exemplo, taxas de frames, resoluções e formato de saída).
// 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() usa três argumentos:
Configuração de stream escolhida pelo caller.
Listener para receber eventos de stream.
Ponteiro para uma configuração de stream efetiva. Recomendamos que o chamador examine esse valor para processar os buffers de frames futuros conforme o esperado.
Quando CameraDevice::start() inicia um fluxo de dados com a plataforma
Camera Service, ele mantém uma referência fraca ao objeto listener do caller para detectar
o encerramento inesperado do caller.
Quando um cliente termina de usar um buffer de frame, ele precisa notificar um dispositivo de câmera
que não precisa mais do buffer de frame chamando o método
CameraDevice::doneWithFrame().
Quando um stream é iniciado, um cliente recebe mensagens de evento. Uma mensagem comum é um novo framebuffer. Por uma função de callback registrada, um cliente recebe um evento
kNewFrameBuffer que contém os dados de imagens e os metadados do
buffer de frames. StreamEventType declara mais tipos para processar outros eventos de
fluxo. Como um fluxo de dados interrompido ou travado.
// 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;
};
Este exemplo mostra uma implementação da interface CameraInputBlock e do app dela:
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.
}
Desempenho
A visibilidade traseira atende a essas regulamentações governamentais.
| Valor | Regulamentações |
|---|---|
| Tempo de resposta | CFR 571.111 S5.5.3 |
| Frame rate | UNECE R46 6.2.2.3.4 |
| Tempo de formação da imagem | UNECE R46 6.2.2.3.4.2 |
| Latência do sistema | UNECE R46 6.2.2.3.4.3 |
Privacidade
Especificamente sobre privacidade:
A API não exige implementações para coletar, registrar ou armazenar informações de identificação pessoal (PII). No entanto, como os dados de imagens capturadas (ou metadados associados) podem conter PII, o app que usa a API precisa receber o consentimento explícito do usuário.
Os usuários não podem controlar os dispositivos de câmera para visualizar no painel de instrumentos porque as câmeras estão envolvidas em funções críticas para a segurança. Os OEMs recebem o consentimento do usuário durante a configuração ou do motorista.
Essa API não é compatível com clientes de câmera em segundo plano. Portanto, o indicador de privacidade, que informa aos usuários que um dispositivo de câmera está capturando dados, está fora do escopo.