Message ID | 20200709091555.1617-3-david.plowman@raspberrypi.com |
---|---|
State | Superseded |
Headers | show |
Series |
|
Related | show |
Hi David, I've just been chatting briefly with Laurent and he has mentioned that this topic should be integrated with the work that Jacopo has done on Pixel Array properties. I haven't looked at that work yet, so I'm not (yet) in a position to comment on that directly, but as I'd already written some generic (libcamera) framework comments about the Rectangle class below, I thought I should send this now... On 09/07/2020 10:15, David Plowman wrote: > These changes implement digital zoom for the Raspberry Pi. We detect > the digital zoom control and update the V4L2 "selection" before > starting the ISP. We also update the value in the control that we send > to the IPA, so that it has the correct value. > --- > include/libcamera/ipa/raspberrypi.h | 1 + > src/ipa/raspberrypi/raspberrypi.cpp | 10 ++++ > .../pipeline/raspberrypi/raspberrypi.cpp | 56 ++++++++++++++++++- > 3 files changed, 66 insertions(+), 1 deletion(-) > > diff --git a/include/libcamera/ipa/raspberrypi.h b/include/libcamera/ipa/raspberrypi.h > index a18ce9a..e66402e 100644 > --- a/include/libcamera/ipa/raspberrypi.h > +++ b/include/libcamera/ipa/raspberrypi.h > @@ -52,6 +52,7 @@ static const ControlInfoMap RPiControls = { > { &controls::Contrast, ControlInfo(0.0f, 32.0f) }, > { &controls::Saturation, ControlInfo(0.0f, 32.0f) }, > { &controls::Sharpness, ControlInfo(0.0f, 16.0f, 1.0f) }, > + { &controls::DigitalZoom, ControlInfo(Rectangle{ 0, 0, 0, 0 }, Rectangle{ 65535, 65535, 65535, 65535 }, Rectangle{ 0, 0, 0, 0 }) }, I wonder if we should have some constructor helpers to make that easier. (Rectangle(0), Rectangle(65535), Rectangle(0)) Oddly, (and this is not a fault of this patch), the idea of a Rectangle which starts at x,y = {65535,65535}, and is of size {65535,65535} seems wrong, but of course this is jsut storage of maximum values. I can't currently think of a better way of storing that, and of course the control has to store the max value like that anyway, so it's not an issue I don't think - just a quirk we might have to be aware of. > }; > > } /* namespace libcamera */ > diff --git a/src/ipa/raspberrypi/raspberrypi.cpp b/src/ipa/raspberrypi/raspberrypi.cpp > index b1f2786..3c68078 100644 > --- a/src/ipa/raspberrypi/raspberrypi.cpp > +++ b/src/ipa/raspberrypi/raspberrypi.cpp > @@ -658,6 +658,16 @@ void IPARPi::queueRequest(const ControlList &controls) > break; > } > > + case controls::DIGITAL_ZOOM: { > + /* > + * We send the zoom info back in the metadata where the pipeline > + * handler will update it to the values actually used. > + */ > + Rectangle crop = ctrl.second.get<Rectangle>(); > + libcameraMetadata_.set(controls::DigitalZoom, crop); > + 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 f4966f8..55db11d 100644 > --- a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp > +++ b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp > @@ -287,7 +287,8 @@ class RPiCameraData : public CameraData > public: > RPiCameraData(PipelineHandler *pipe) > : CameraData(pipe), sensor_(nullptr), lsTable_(nullptr), > - state_(State::Stopped), dropFrame_(false), ispOutputCount_(0) > + state_(State::Stopped), dropFrame_(false), ispOutputCount_(0), > + ispMinSize_(0) > { > } > > @@ -322,6 +323,14 @@ public: > void handleStreamBuffer(FrameBuffer *buffer, const RPiStream *stream); > void handleState(); > > + void initSensorCrop(Rectangle const &crop, unsigned int ispMinSize) > + { > + /* The initial zoom region is the whole of the sensorCrop_. */ > + sensorCrop_ = crop; > + zoomRect_ = Rectangle{ 0, 0, crop.width, crop.height }; > + ispMinSize_ = ispMinSize; > + } > + > CameraSensor *sensor_; > /* Array of Unicam and ISP device streams and associated buffers/streams. */ > RPiDevice<Unicam, 2> unicam_; > @@ -358,6 +367,15 @@ private: > > bool dropFrame_; > int ispOutputCount_; > + /* > + * sensorCrop_ is the largest region of the full sensor output that matches > + * the desired aspect ratio, and therefore represents the area within > + * which we can pan and zoom. zoomRect_ is the portion from within the > + * sensorCrop_ that pan/zoom is currently using. > + */ > + Rectangle sensorCrop_; > + Rectangle zoomRect_; > + unsigned int ispMinSize_; > }; > > class RPiCameraConfiguration : public CameraConfiguration > @@ -744,6 +762,10 @@ int PipelineHandlerRPi::configure(Camera *camera, CameraConfiguration *config) > return ret; > } > > + Rectangle testCrop = { 0, 0, 1, 1 }; > + data->isp_[Isp::Input].dev()->setSelection(V4L2_SEL_TGT_CROP, &testCrop); > + const unsigned int ispMinSize = testCrop.width; > + > /* Adjust aspect ratio by providing crops on the input image. */ > Rectangle crop = { > .x = 0, > @@ -763,8 +785,12 @@ int PipelineHandlerRPi::configure(Camera *camera, CameraConfiguration *config) > > 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); > > + sensorCrop_ = Size(crop.width, crop.height); > + data->initSensorCrop(crop, ispMinSize); > + > ret = configureIPA(camera); > if (ret) > LOG(RPI, Error) << "Failed to configure the IPA: " << ret; > @@ -1553,6 +1579,34 @@ void RPiCameraData::tryRunPipeline() > */ > Request *request = requestQueue_.front(); > > + if (request->controls().contains(controls::DigitalZoom)) { > + Rectangle rect = request->controls().get<Rectangle>(controls::DigitalZoom); > + /* > + * If a new digital zoom value was given, check that it lies within the > + * available sensorCrop_, coercing it if necessary. > + */ > + if (rect.width && rect.height) { Laurent has very recently added a helper for the Size class as isNull. I suspect we could add in a Rectangle.isNull() to make that statement if (!rect.isNull()) Of course, there could be intricacies of is a rectangle null only if the width/height are 0, but position could be set or does the position have to also be 0,0... > + zoomRect_ = rect; > + if (zoomRect_.width < ispMinSize_) > + zoomRect_.width = ispMinSize_; > + else if (zoomRect_.width > sensorCrop_.width) > + zoomRect_.width = sensorCrop_.width; > + if (zoomRect_.height < ispMinSize_) > + zoomRect_.height = ispMinSize_; > + else if (zoomRect_.height > sensorCrop_.height) > + zoomRect_.height = sensorCrop_.height; > + if (zoomRect_.x + zoomRect_.width > sensorCrop_.width) > + zoomRect_.x = sensorCrop_.width - zoomRect_.width; > + if (zoomRect_.y + zoomRect_.height > sensorCrop_.height) > + zoomRect_.y = sensorCrop_.height - zoomRect_.height; Ayeee, and that is begging for some rectangle .clip(rect2) or .clamp(rect2) helpers to be added to include/libcamera/geometry.h ... > + } > + Rectangle sensorRect = zoomRect_; > + sensorRect.x += sensorCrop_.x; > + sensorRect.y += sensorCrop_.y; > + isp_[Isp::Input].dev()->setSelection(V4L2_SEL_TGT_CROP, &sensorRect); > + request->controls().set(controls::DigitalZoom, zoomRect_); > + } > + > /* > * 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 >
Hi Kieran Thanks for the Rectangle constructors that I've seen appear in the code, that's obviously helpful. Also the suggestion on adding a clip function to the Rectangle class is clearly sensible - so I'll include that in the next version of patches! Best regards David On Fri, 10 Jul 2020 at 14:40, Kieran Bingham <kieran.bingham@ideasonboard.com> wrote: > > Hi David, > > I've just been chatting briefly with Laurent and he has mentioned that > this topic should be integrated with the work that Jacopo has done on > Pixel Array properties. > > I haven't looked at that work yet, so I'm not (yet) in a position to > comment on that directly, but as I'd already written some generic > (libcamera) framework comments about the Rectangle class below, I > thought I should send this now... > > > > On 09/07/2020 10:15, David Plowman wrote: > > These changes implement digital zoom for the Raspberry Pi. We detect > > the digital zoom control and update the V4L2 "selection" before > > starting the ISP. We also update the value in the control that we send > > to the IPA, so that it has the correct value. > > --- > > include/libcamera/ipa/raspberrypi.h | 1 + > > src/ipa/raspberrypi/raspberrypi.cpp | 10 ++++ > > .../pipeline/raspberrypi/raspberrypi.cpp | 56 ++++++++++++++++++- > > 3 files changed, 66 insertions(+), 1 deletion(-) > > > > diff --git a/include/libcamera/ipa/raspberrypi.h b/include/libcamera/ipa/raspberrypi.h > > index a18ce9a..e66402e 100644 > > --- a/include/libcamera/ipa/raspberrypi.h > > +++ b/include/libcamera/ipa/raspberrypi.h > > @@ -52,6 +52,7 @@ static const ControlInfoMap RPiControls = { > > { &controls::Contrast, ControlInfo(0.0f, 32.0f) }, > > { &controls::Saturation, ControlInfo(0.0f, 32.0f) }, > > { &controls::Sharpness, ControlInfo(0.0f, 16.0f, 1.0f) }, > > + { &controls::DigitalZoom, ControlInfo(Rectangle{ 0, 0, 0, 0 }, Rectangle{ 65535, 65535, 65535, 65535 }, Rectangle{ 0, 0, 0, 0 }) }, > > I wonder if we should have some constructor helpers to make that easier. > (Rectangle(0), Rectangle(65535), Rectangle(0)) > > > Oddly, (and this is not a fault of this patch), the idea of a Rectangle > which starts at x,y = {65535,65535}, and is of size {65535,65535} seems > wrong, but of course this is jsut storage of maximum values. > > I can't currently think of a better way of storing that, and of course > the control has to store the max value like that anyway, so it's not an > issue I don't think - just a quirk we might have to be aware of. > > > > > }; > > > > } /* namespace libcamera */ > > diff --git a/src/ipa/raspberrypi/raspberrypi.cpp b/src/ipa/raspberrypi/raspberrypi.cpp > > index b1f2786..3c68078 100644 > > --- a/src/ipa/raspberrypi/raspberrypi.cpp > > +++ b/src/ipa/raspberrypi/raspberrypi.cpp > > @@ -658,6 +658,16 @@ void IPARPi::queueRequest(const ControlList &controls) > > break; > > } > > > > + case controls::DIGITAL_ZOOM: { > > + /* > > + * We send the zoom info back in the metadata where the pipeline > > + * handler will update it to the values actually used. > > + */ > > + Rectangle crop = ctrl.second.get<Rectangle>(); > > + libcameraMetadata_.set(controls::DigitalZoom, crop); > > + 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 f4966f8..55db11d 100644 > > --- a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp > > +++ b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp > > @@ -287,7 +287,8 @@ class RPiCameraData : public CameraData > > public: > > RPiCameraData(PipelineHandler *pipe) > > : CameraData(pipe), sensor_(nullptr), lsTable_(nullptr), > > - state_(State::Stopped), dropFrame_(false), ispOutputCount_(0) > > + state_(State::Stopped), dropFrame_(false), ispOutputCount_(0), > > + ispMinSize_(0) > > { > > } > > > > @@ -322,6 +323,14 @@ public: > > void handleStreamBuffer(FrameBuffer *buffer, const RPiStream *stream); > > void handleState(); > > > > + void initSensorCrop(Rectangle const &crop, unsigned int ispMinSize) > > + { > > + /* The initial zoom region is the whole of the sensorCrop_. */ > > + sensorCrop_ = crop; > > + zoomRect_ = Rectangle{ 0, 0, crop.width, crop.height }; > > + ispMinSize_ = ispMinSize; > > + } > > + > > CameraSensor *sensor_; > > /* Array of Unicam and ISP device streams and associated buffers/streams. */ > > RPiDevice<Unicam, 2> unicam_; > > @@ -358,6 +367,15 @@ private: > > > > bool dropFrame_; > > int ispOutputCount_; > > + /* > > + * sensorCrop_ is the largest region of the full sensor output that matches > > + * the desired aspect ratio, and therefore represents the area within > > + * which we can pan and zoom. zoomRect_ is the portion from within the > > + * sensorCrop_ that pan/zoom is currently using. > > + */ > > + Rectangle sensorCrop_; > > + Rectangle zoomRect_; > > + unsigned int ispMinSize_; > > }; > > > > class RPiCameraConfiguration : public CameraConfiguration > > @@ -744,6 +762,10 @@ int PipelineHandlerRPi::configure(Camera *camera, CameraConfiguration *config) > > return ret; > > } > > > > + Rectangle testCrop = { 0, 0, 1, 1 }; > > + data->isp_[Isp::Input].dev()->setSelection(V4L2_SEL_TGT_CROP, &testCrop); > > + const unsigned int ispMinSize = testCrop.width; > > + > > /* Adjust aspect ratio by providing crops on the input image. */ > > Rectangle crop = { > > .x = 0, > > @@ -763,8 +785,12 @@ int PipelineHandlerRPi::configure(Camera *camera, CameraConfiguration *config) > > > > 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); > > > > + sensorCrop_ = Size(crop.width, crop.height); > > + data->initSensorCrop(crop, ispMinSize); > > + > > ret = configureIPA(camera); > > if (ret) > > LOG(RPI, Error) << "Failed to configure the IPA: " << ret; > > @@ -1553,6 +1579,34 @@ void RPiCameraData::tryRunPipeline() > > */ > > Request *request = requestQueue_.front(); > > > > + if (request->controls().contains(controls::DigitalZoom)) { > > + Rectangle rect = request->controls().get<Rectangle>(controls::DigitalZoom); > > + /* > > + * If a new digital zoom value was given, check that it lies within the > > + * available sensorCrop_, coercing it if necessary. > > + */ > > + if (rect.width && rect.height) { > > Laurent has very recently added a helper for the Size class as isNull. > > I suspect we could add in a Rectangle.isNull() to make that statement > if (!rect.isNull()) > > Of course, there could be intricacies of is a rectangle null only if the > width/height are 0, but position could be set or does the position have > to also be 0,0... > > > > > + zoomRect_ = rect; > > + if (zoomRect_.width < ispMinSize_) > > + zoomRect_.width = ispMinSize_; > > + else if (zoomRect_.width > sensorCrop_.width) > > + zoomRect_.width = sensorCrop_.width; > > + if (zoomRect_.height < ispMinSize_) > > + zoomRect_.height = ispMinSize_; > > + else if (zoomRect_.height > sensorCrop_.height) > > + zoomRect_.height = sensorCrop_.height; > > + if (zoomRect_.x + zoomRect_.width > sensorCrop_.width) > > + zoomRect_.x = sensorCrop_.width - zoomRect_.width; > > + if (zoomRect_.y + zoomRect_.height > sensorCrop_.height) > > + zoomRect_.y = sensorCrop_.height - zoomRect_.height; > > Ayeee, and that is begging for some rectangle .clip(rect2) or > .clamp(rect2) helpers to be added to include/libcamera/geometry.h ... > > > > + } > > + Rectangle sensorRect = zoomRect_; > > + sensorRect.x += sensorCrop_.x; > > + sensorRect.y += sensorCrop_.y; > > + isp_[Isp::Input].dev()->setSelection(V4L2_SEL_TGT_CROP, &sensorRect); > > + request->controls().set(controls::DigitalZoom, zoomRect_); > > + } > > + > > /* > > * 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 > -- > Kieran
diff --git a/include/libcamera/ipa/raspberrypi.h b/include/libcamera/ipa/raspberrypi.h index a18ce9a..e66402e 100644 --- a/include/libcamera/ipa/raspberrypi.h +++ b/include/libcamera/ipa/raspberrypi.h @@ -52,6 +52,7 @@ static const ControlInfoMap RPiControls = { { &controls::Contrast, ControlInfo(0.0f, 32.0f) }, { &controls::Saturation, ControlInfo(0.0f, 32.0f) }, { &controls::Sharpness, ControlInfo(0.0f, 16.0f, 1.0f) }, + { &controls::DigitalZoom, ControlInfo(Rectangle{ 0, 0, 0, 0 }, Rectangle{ 65535, 65535, 65535, 65535 }, Rectangle{ 0, 0, 0, 0 }) }, }; } /* namespace libcamera */ diff --git a/src/ipa/raspberrypi/raspberrypi.cpp b/src/ipa/raspberrypi/raspberrypi.cpp index b1f2786..3c68078 100644 --- a/src/ipa/raspberrypi/raspberrypi.cpp +++ b/src/ipa/raspberrypi/raspberrypi.cpp @@ -658,6 +658,16 @@ void IPARPi::queueRequest(const ControlList &controls) break; } + case controls::DIGITAL_ZOOM: { + /* + * We send the zoom info back in the metadata where the pipeline + * handler will update it to the values actually used. + */ + Rectangle crop = ctrl.second.get<Rectangle>(); + libcameraMetadata_.set(controls::DigitalZoom, crop); + 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 f4966f8..55db11d 100644 --- a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp +++ b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp @@ -287,7 +287,8 @@ class RPiCameraData : public CameraData public: RPiCameraData(PipelineHandler *pipe) : CameraData(pipe), sensor_(nullptr), lsTable_(nullptr), - state_(State::Stopped), dropFrame_(false), ispOutputCount_(0) + state_(State::Stopped), dropFrame_(false), ispOutputCount_(0), + ispMinSize_(0) { } @@ -322,6 +323,14 @@ public: void handleStreamBuffer(FrameBuffer *buffer, const RPiStream *stream); void handleState(); + void initSensorCrop(Rectangle const &crop, unsigned int ispMinSize) + { + /* The initial zoom region is the whole of the sensorCrop_. */ + sensorCrop_ = crop; + zoomRect_ = Rectangle{ 0, 0, crop.width, crop.height }; + ispMinSize_ = ispMinSize; + } + CameraSensor *sensor_; /* Array of Unicam and ISP device streams and associated buffers/streams. */ RPiDevice<Unicam, 2> unicam_; @@ -358,6 +367,15 @@ private: bool dropFrame_; int ispOutputCount_; + /* + * sensorCrop_ is the largest region of the full sensor output that matches + * the desired aspect ratio, and therefore represents the area within + * which we can pan and zoom. zoomRect_ is the portion from within the + * sensorCrop_ that pan/zoom is currently using. + */ + Rectangle sensorCrop_; + Rectangle zoomRect_; + unsigned int ispMinSize_; }; class RPiCameraConfiguration : public CameraConfiguration @@ -744,6 +762,10 @@ int PipelineHandlerRPi::configure(Camera *camera, CameraConfiguration *config) return ret; } + Rectangle testCrop = { 0, 0, 1, 1 }; + data->isp_[Isp::Input].dev()->setSelection(V4L2_SEL_TGT_CROP, &testCrop); + const unsigned int ispMinSize = testCrop.width; + /* Adjust aspect ratio by providing crops on the input image. */ Rectangle crop = { .x = 0, @@ -763,8 +785,12 @@ int PipelineHandlerRPi::configure(Camera *camera, CameraConfiguration *config) 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); + sensorCrop_ = Size(crop.width, crop.height); + data->initSensorCrop(crop, ispMinSize); + ret = configureIPA(camera); if (ret) LOG(RPI, Error) << "Failed to configure the IPA: " << ret; @@ -1553,6 +1579,34 @@ void RPiCameraData::tryRunPipeline() */ Request *request = requestQueue_.front(); + if (request->controls().contains(controls::DigitalZoom)) { + Rectangle rect = request->controls().get<Rectangle>(controls::DigitalZoom); + /* + * If a new digital zoom value was given, check that it lies within the + * available sensorCrop_, coercing it if necessary. + */ + if (rect.width && rect.height) { + zoomRect_ = rect; + if (zoomRect_.width < ispMinSize_) + zoomRect_.width = ispMinSize_; + else if (zoomRect_.width > sensorCrop_.width) + zoomRect_.width = sensorCrop_.width; + if (zoomRect_.height < ispMinSize_) + zoomRect_.height = ispMinSize_; + else if (zoomRect_.height > sensorCrop_.height) + zoomRect_.height = sensorCrop_.height; + if (zoomRect_.x + zoomRect_.width > sensorCrop_.width) + zoomRect_.x = sensorCrop_.width - zoomRect_.width; + if (zoomRect_.y + zoomRect_.height > sensorCrop_.height) + zoomRect_.y = sensorCrop_.height - zoomRect_.height; + } + Rectangle sensorRect = zoomRect_; + sensorRect.x += sensorCrop_.x; + sensorRect.y += sensorCrop_.y; + isp_[Isp::Input].dev()->setSelection(V4L2_SEL_TGT_CROP, &sensorRect); + request->controls().set(controls::DigitalZoom, zoomRect_); + } + /* * 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