[libcamera-devel] [PATCH v5 5/5] libcamera: pipeline: raspberrypi: Implementation of digital zoom

David Plowman david.plowman at raspberrypi.com
Sat Oct 24 23:51:47 CEST 2020


Hi Jacopo

Thanks for the comments. Yes, this is an interesting point (see below...)

On Sat, 24 Oct 2020 at 17:56, Jacopo Mondi <jacopo at jmondi.org> wrote:
>
> Hi David, Laurent,
>
> On Sat, Oct 24, 2020 at 02:31:09AM +0300, Laurent Pinchart wrote:
> > Hi David,
> >
> > Thank you for the patch.
> >
> > On Fri, Oct 23, 2020 at 11:21:59AM +0100, David Plowman wrote:
> > > During configure() we update the ScalerCropMaximum to the correct
> > > value for this camera mode and work out the minimum crop size allowed
> > > by the ISP.
> > >
> > > Whenever a new ScalerCrop request is received we check it's valid and
> > > apply it to the ISP V4L2 device. When the IPA returns its metadata to
> > > us we add the ScalerCrop information, rescaled to sensor native
> > > pixels.
> > >
> > > Signed-off-by: David Plowman <david.plowman at raspberrypi.com>
>
> For the patch
> Reviewed-by: Jacopo Mondi <jacopo at jmondi.org>
>
> but to add to the discussion
>
> > > ---
> > >  include/libcamera/ipa/raspberrypi.h           |  1 +
> > >  src/ipa/raspberrypi/raspberrypi.cpp           |  5 ++
> > >  .../pipeline/raspberrypi/raspberrypi.cpp      | 82 +++++++++++++++----
> > >  3 files changed, 72 insertions(+), 16 deletions(-)
> > >
> > > diff --git a/include/libcamera/ipa/raspberrypi.h b/include/libcamera/ipa/raspberrypi.h
> > > index b23baf2f..ff2faf86 100644
> > > --- a/include/libcamera/ipa/raspberrypi.h
> > > +++ b/include/libcamera/ipa/raspberrypi.h
> > > @@ -62,6 +62,7 @@ static const ControlInfoMap Controls = {
> > >     { &controls::Saturation, ControlInfo(0.0f, 32.0f) },
> > >     { &controls::Sharpness, ControlInfo(0.0f, 16.0f, 1.0f) },
> > >     { &controls::ColourCorrectionMatrix, ControlInfo(-16.0f, 16.0f) },
> > > +   { &controls::ScalerCrop, ControlInfo(Rectangle{}, Rectangle(65535, 65535, 65535, 65535), Rectangle{}) },
> > >  };
> > >
> > >  } /* namespace RPi */
> > > diff --git a/src/ipa/raspberrypi/raspberrypi.cpp b/src/ipa/raspberrypi/raspberrypi.cpp
> > > index 1c255aa2..f338ff8b 100644
> > > --- a/src/ipa/raspberrypi/raspberrypi.cpp
> > > +++ b/src/ipa/raspberrypi/raspberrypi.cpp
> > > @@ -699,6 +699,11 @@ void IPARPi::queueRequest(const ControlList &controls)
> > >                     break;
> > >             }
> > >
> > > +           case controls::SCALER_CROP: {
> > > +                   /* We do nothing with this, but should avoid the warning below. */
> > > +                   break;
> > > +           }
> > > +
> > >             default:
> > >                     LOG(IPARPI, Warning)
> > >                             << "Ctrl " << controls::controls.at(ctrl.first)->name()
> > > diff --git a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
> > > index 763451a8..83e91576 100644
> > > --- a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
> > > +++ b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
> > > @@ -193,6 +193,11 @@ public:
> > >     bool flipsAlterBayerOrder_;
> > >     BayerFormat::Order nativeBayerOrder_;
> > >
> > > +   /* For handling digital zoom. */
> > > +   CameraSensorInfo sensorInfo_;
> > > +   Rectangle lastIspCrop_;
> > > +   Size ispMinSize_;
> >
> > Maybe ispMinCropSize_ ?
> >
> > > +
> > >     unsigned int dropFrameCount_;
> > >
> > >  private:
> > > @@ -677,26 +682,31 @@ int PipelineHandlerRPi::configure(Camera *camera, CameraConfiguration *config)
> > >             return ret;
> > >     }
> > >
> > > -   /* Adjust aspect ratio by providing crops on the input image. */
> > > -   Rectangle crop{ 0, 0, sensorFormat.size };
> > > -
> > > -   int ar = maxSize.height * sensorFormat.size.width - maxSize.width * sensorFormat.size.height;
> > > -   if (ar > 0)
> > > -           crop.width = maxSize.width * sensorFormat.size.height / maxSize.height;
> > > -   else if (ar < 0)
> > > -           crop.height = maxSize.height * sensorFormat.size.width / maxSize.width;
> > > +   /* Figure out the smallest selection the ISP will allow. */
> > > +   Rectangle testCrop(0, 0, 1, 1);
> > > +   data->isp_[Isp::Input].dev()->setSelection(V4L2_SEL_TGT_CROP, &testCrop);
> > > +   data->ispMinSize_ = testCrop.size();
> > >
> > > -   crop.width &= ~1;
> > > -   crop.height &= ~1;
> > > +   /* Adjust aspect ratio by providing crops on the input image. */
> > > +   Size size = sensorFormat.size.boundedToAspectRatio(maxSize);
> > > +   Rectangle crop = size.centeredTo(sensorFormat.size.center());
> > > +   data->lastIspCrop_ = crop;
> > >
> > > -   crop.x = (sensorFormat.size.width - crop.width) >> 1;
> > > -   crop.y = (sensorFormat.size.height - crop.height) >> 1;
> > >     data->isp_[Isp::Input].dev()->setSelection(V4L2_SEL_TGT_CROP, &crop);
> > >
> > >     ret = data->configureIPA(config);
> > >     if (ret)
> > >             LOG(RPI, Error) << "Failed to configure the IPA: " << ret;
> > >
> > > +   /*
> > > +    * Update the ScalerCropMaximum to the correct value for this camera mode.
> > > +    * For us, it's the same as the "analogue crop".
> > > +    *
> > > +    * \todo Make this property the ScalerCrop maximum value when dynamic
> > > +    * controls are available and set it at validate() time
> > > +    */
> > > +   data->properties_.set(properties::ScalerCropMaximum, data->sensorInfo_.analogCrop);
> > > +
> > >     return ret;
> > >  }
> > >
> > > @@ -1154,8 +1164,8 @@ int RPiCameraData::configureIPA(const CameraConfiguration *config)
> > >             ipaConfig.data.push_back(static_cast<unsigned int>(lsTable_.fd()));
> > >     }
> > >
> > > -   CameraSensorInfo sensorInfo = {};
> > > -   int ret = sensor_->sensorInfo(&sensorInfo);
> > > +   /* We store the CameraSensorInfo for digital zoom calculations. */
> > > +   int ret = sensor_->sensorInfo(&sensorInfo_);
> > >     if (ret) {
> > >             LOG(RPI, Error) << "Failed to retrieve camera sensor info";
> > >             return ret;
> > > @@ -1164,7 +1174,7 @@ int RPiCameraData::configureIPA(const CameraConfiguration *config)
> > >     /* Ready the IPA - it must know about the sensor resolution. */
> > >     IPAOperationData result;
> > >
> > > -   ipa_->configure(sensorInfo, streamConfig, entityControls, ipaConfig,
> > > +   ipa_->configure(sensorInfo_, streamConfig, entityControls, ipaConfig,
> > >                     &result);
> > >
> > >     unsigned int resultIdx = 0;
> > > @@ -1243,8 +1253,23 @@ void RPiCameraData::queueFrameAction([[maybe_unused]] unsigned int frame,
> > >             FrameBuffer *buffer = isp_[Isp::Stats].getBuffers().at(bufferId);
> > >
> > >             handleStreamBuffer(buffer, &isp_[Isp::Stats]);
> > > +
> > >             /* Fill the Request metadata buffer with what the IPA has provided */
> > > -           requestQueue_.front()->metadata() = std::move(action.controls[0]);
> > > +           Request *request = requestQueue_.front();
> > > +           request->metadata() = std::move(action.controls[0]);
> > > +
> > > +           /*
> > > +            * Also update the ScalerCrop in the metadata with what we actually
> > > +            * used. But we must first rescale that from ISP (camera mode) pixels
> > > +            * back into sensor native pixels.
> > > +            *
> > > +            * Sending this information on every frame may be helpful.
> > > +            */
> > > +           Rectangle crop = lastIspCrop_.scaledBy(sensorInfo_.analogCrop.size(),
> > > +                                                  sensorInfo_.outputSize);
> > > +           crop.translateBy(sensorInfo_.analogCrop.topLeft());
> >
> > Would it make sense to store this in lastCrop_, along with lastIspCrop_,
> > to avoid recomputing it in every frame ?
> >
> > > +           request->metadata().set(controls::ScalerCrop, crop);
> > > +
> > >             state_ = State::IpaComplete;
> > >             break;
> > >     }
> > > @@ -1595,6 +1620,31 @@ void RPiCameraData::tryRunPipeline()
> > >     /* Take the first request from the queue and action the IPA. */
> > >     Request *request = requestQueue_.front();
> > >
> > > +   if (request->controls().contains(controls::ScalerCrop)) {
> > > +           Rectangle crop = request->controls().get<Rectangle>(controls::ScalerCrop);
> > > +
> > > +           if (crop.width && crop.height) {
> >
> > Something we can address on top of this series, but I'd like to
> > explicitly document how this case is to be handled, from an API point of
> > view. The question goes beyond digital zoom: how should libcamera handle
> > invalid control values ?
> >
> > In this specific case, the value can't be considered invalid as
> > include/libcamera/ipa/raspberrypi.h sets the minimum to Rectangle{}, so
> > width == 0 || height == 0 is valid from that point of view. Practically
> > speaking that's of course not valid, and I think it would make sense to
> > set the minimum to the hardware limit if possible.
> >
> > The question still holds, how should we react to invalid control values
> > ? Should Camera::queueRequest() return an error ? That may be possible
> > in some cases, such as checking against the boundaries set by the
> > pipeline handler in the ControlInfoMap (and we'll possibly have a tiny
> > race condition to handle there if we allow pipeline handlers to update
> > the ControlInfoMap after start()), but not in all cases as the pipeline
> > handler isn't involved in the synchronous part of
> > Camera::queueRequest(). We could of course extend the pipeline handler
> > API with a function to validate controls synchronously.
> >
> > Another option is to fail the request asynchronously, reporting an
> > error. A third option is to proceeds with the control being either
> > ignored, or set to the nearest acceptable value. I'm sure there could be
> > other options, such as picking a default value for instance.
> >
>
> How is digital zoom supposed to be reset ? Should the application
> reset it to the full active pixel array size ? Should a (0, 0)
> Rectangle reset the scaler crop otherwise ?
>

Setting the ScalerCrop to the ScalerCropMaximum might seem the
"obvious" thing to do, but in general it wouldn't have the right
aspect ratio. So we could go with a rectangle full of zeroes (or at
least width and height zero) to mean "the default, please".

Of course, we haven't defined what the default is. You might expect to
crop from the middle of the ScalerCropMaximum, but we don't mandate
that. Nor is it clear how you would find out. (Although in the Pi
version I always return the ScalerCrop - it seems to me like a useful
thing. But I expect that behaviour is not guaranteed for other
pipelines, even if they support the ScalerCrop.)

However, I suspect that applications that want to take control of the
zoom will probably do so completely. They'll implement their own zoom
code and may as well implement their own default by setting their zoom
factor to 1, regardless of any pipeline default. So I think it may be
less important in practice, even if it does seem like a bit of an
omission.

Nevertheless, having all-zeroes be a request for the default seems
reasonable enough to me. If there are no objections I could add that
into the next version of the patches.

Thanks!
David

> Thanks
>   j
>
> > > +                   /* First scale the crop from sensor native to camera mode pixels. */
> > > +                   crop.translateBy(-sensorInfo_.analogCrop.topLeft());
> > > +                   crop.scaleBy(sensorInfo_.outputSize, sensorInfo_.analogCrop.size());
> > > +
> > > +                   /*
> > > +                    * The crop that we set must be:
> > > +                    * 1. At least as big as ispMinSize_, once that's been
> > > +                    *    enlarged to the same aspect ratio.
> > > +                    * 2. With the same mid-point, if possible.
> > > +                    * 3. But it can't go outside the sensor area.
> > > +                    */
> > > +                   Size minSize = ispMinSize_.expandedToAspectRatio(crop.size());
> > > +                   Size size = crop.size().expandedTo(minSize);
> > > +                   crop = size.centeredTo(crop.center()).enclosedIn(Rectangle(sensorInfo_.outputSize));
> > > +
> > > +                   if (crop != lastIspCrop_)
> > > +                           isp_[Isp::Input].dev()->setSelection(V4L2_SEL_TGT_CROP, &crop);
> > > +                   lastIspCrop_ = crop;
> > > +           }
> > > +   }
> > > +
> > >     /*
> > >      * Process all the user controls by the IPA. Once this is complete, we
> > >      * queue the ISP output buffer listed in the request to start the HW
> >
> > --
> > Regards,
> >
> > Laurent Pinchart
> > _______________________________________________
> > libcamera-devel mailing list
> > libcamera-devel at lists.libcamera.org
> > https://lists.libcamera.org/listinfo/libcamera-devel


More information about the libcamera-devel mailing list