[libcamera-devel] Software gathering of image statistics for libcamera on Librem 5

Laurent Pinchart laurent.pinchart at ideasonboard.com
Sun Dec 11 00:40:43 CET 2022


Hi Pavel,

On Sat, Dec 10, 2022 at 10:09:20PM +0100, Pavel Machek via libcamera-devel wrote:
> Hi!
> 
> > > Thanks for rkisp pointer. I'm quite confused by that code, not knowing
> > > much about rkisp. Does it have some special support in hardware, like
> > > providing one stream of frames for application and second for
> > > statistics gathering?
> > 
> > Yes. But when capturing RAW frames, it doesn't have the ability to
> > provide statistics, so that means for RAW capture - we can't have a 3a
> > loop running, and can only run with manual parameters.
> > 
> > > For Librem 5 (and possibly PinePhone and similar), I'd like to snoop
> > > on frames provided to the application, and set up exposure
> > > accordingly. But I don't see any code in libcamera accessing image
> > > data.
> > > 
> > > Closest I could get is Image class (src/apps/common/image.h) used by
> > > file sinks in cam application. Would it be acceptable to move that
> > > into libcamera and use it to peek at the pixels?
> > 
> > You can peek at pixels with the MappedFrameBuffer class.
> > 
> >  include/libcamera/internal/mapped_framebuffer.h
> >  src/libcamera/mapped_framebuffer.cpp
> > 
> > If we only need 200 pixels to process, I'd be very happy to see a CPU
> > based implementation that could be used on 'any' platform without an
> > ISP.
> 
> Thank you. It looks like I can access all the neccessary information
> from simple pipeline. I guess MappedPixels class should go somewhere
> else; let me know if you have preferences for place and naming.

Please see my other reply in this mail thread. I would like to see a
base abstract class to model a software ISP.

> (There's more work to do, this just shows I have correct hooks).
> 
> Best regards,
> 								Pavel
> 
> diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
> index a32de7f3..2980c636 100644
> --- a/src/libcamera/pipeline/simple/simple.cpp
> +++ b/src/libcamera/pipeline/simple/simple.cpp
> @@ -27,6 +27,7 @@
>  #include <libcamera/control_ids.h>
>  #include <libcamera/request.h>
>  #include <libcamera/stream.h>
> +#include <libcamera/formats.h>
>  
>  #include "libcamera/internal/camera.h"
>  #include "libcamera/internal/camera_sensor.h"
> @@ -35,6 +36,7 @@
>  #include "libcamera/internal/pipeline_handler.h"
>  #include "libcamera/internal/v4l2_subdevice.h"
>  #include "libcamera/internal/v4l2_videodevice.h"
> +#include "libcamera/internal/mapped_framebuffer.h"
>  
>  #include "converter.h"
>  
> @@ -213,6 +215,7 @@ public:
>  	int setupFormats(V4L2SubdeviceFormat *format,
>  			 V4L2Subdevice::Whence whence);
>  	void bufferReady(FrameBuffer *buffer);
> +	void autoProcessing(Request *request);
>  
>  	unsigned int streamIndex(const Stream *stream) const
>  	{
> @@ -724,6 +727,135 @@ int SimpleCameraData::setupFormats(V4L2SubdeviceFormat *format,
>  	return 0;
>  }
>  
> +class MappedPixels {
> +public:
> +	unsigned char *data;
> +	PixelFormat format;
> +	Size size;
> +	int bpp;
> +	int maxval;
> +  
> +	MappedPixels(unsigned char *_data, const struct StreamConfiguration &_config) {
> +		data = _data;
> +		format = _config.pixelFormat;
> +		size = _config.size;
> +
> +		switch (format) {
> +		case formats::SGRBG8:
> +			bpp = 1;
> +			maxval = 255;
> +			break;
> +		case formats::SGRBG10:
> +			bpp = 2;
> +			maxval = 1023;
> +			break;
> +		default:
> +			LOG(SimplePipeline, Error) << "Mapped pixels " << format << " is unknown format.";
> +		}
> +	}
> +
> +	int getValue(unsigned int x, unsigned int y) {
> +		unsigned int v;
> +		if (x >= size.width)
> +			x = size.width - 1;
> +		if (y >= size.height)
> +			y = size.height - 1;
> +		int i = (x + size.width * y) * bpp;
> +		v = data[i];
> +		if (bpp > 1)
> +			v |= data[i+1] << 8;
> +		return v;
> +	}
> +
> +	int getMaxValue(unsigned int x, unsigned int y) {
> +		int v, v2;
> +
> +		v = getValue(x, y);
> +		v2 = getValue(x+1, y);
> +		if (v2 > v)
> +			v = v2;
> +		v2 = getValue(x, y+1);
> +		if (v2 > v)
> +			v = v2;
> +		v2 = getValue(x+1, y+1);
> +		if (v2 > v)
> +			v = v2;
> +		return v;
> +	}
> +
> +	float getMaxValueR(float x, float y) {
> +		float v = getMaxValue(x * size.width, y * size.height);
> +		return v/maxval;
> +	}
> +
> +	void debugPaint(void) {
> +		char map[] = "  .,:;-+=*#";
> +		for (float y = 0; y < 1; y += 1/25.) {
> +			for (float x = 0; x < 1; x += 1/80.) {
> +				float v = getMaxValueR(x, y);
> +				printf("%c", map[ int (v * (sizeof(map) - 2)) ]);
> +			}
> +			printf("\n");
> +		}
> +	}	
> +};
> +
> +void SimpleCameraData::autoProcessing(Request *request)
> +{
> +	ControlList ctrls = sensor_->getControls({ V4L2_CID_EXPOSURE, V4L2_CID_ANALOGUE_GAIN });
> +	static int exposure = 64;
> +	static int gain = 64;
> +
> +	/* good place to add my processing ? */
> +        for (auto [stream, buffer] : request->buffers()) {
> +		MappedFrameBuffer mappedBuffer(buffer, MappedFrameBuffer::MapFlag::Read);
> +		const std::vector<Span<uint8_t>> &planes = mappedBuffer.planes();
> +		unsigned char *img = planes[0].data();
> +		const struct StreamConfiguration &config = stream->configuration();
> +		MappedPixels pixels(img, config);
> +
> + 		LOG(SimplePipeline, Error) << config.pixelFormat << " " << config.size;
> +
> +		pixels.debugPaint();
> +
> +		int bright = 0, too_bright = 0, total = 0;
> +		int adjust = 0;
> +
> +		for (float y = 0; y < 1; y += 1/30.) {
> +			for (float x = 0; x < 1; x += 1/40.) {
> +				float v = pixels.getMaxValueR(x, y);
> +
> +				total++;
> +				if (v > 240./255)
> +					too_bright++;
> +				if (v > 200./255)
> +					bright++;
> +			}
> +		}
> +
> +		if ((bright / (float) total) < 0.01)
> +			adjust = +1;
> +		if ((too_bright / (float) total) > 0.08)
> +			adjust = -1;
> +
> +		exposure += adjust;
> +		gain += adjust;
> +		printf("image data -- %d %d -- %d -- exp %d\n", 
> +			(int) img[0], (int) img[1], adjust, exposure);
> +
> +		ctrls.set(V4L2_CID_EXPOSURE, exposure);
> +		ctrls.set(V4L2_CID_ANALOGUE_GAIN, gain);
> +	}
> +#if 0
> +	const ControlInfoMap &infoMap = controls();
> +
> +	if (infoMap.find(V4L2_CID_BRIGHTNESS) != infoMap.end()) {
> +		//const ControlInfo &brightness = infoMap.find(V4L2_CID_BRIGHTNESS)->second;
> +	}
> +#endif
> +
> +}
> +
>  void SimpleCameraData::bufferReady(FrameBuffer *buffer)
>  {
>  	SimplePipelineHandler *pipe = SimpleCameraData::pipe();
> @@ -739,6 +871,7 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer)
>  			Request *request = buffer->request();
>  			pipe->completeBuffer(request, buffer);
>  			pipe->completeRequest(request);
> +
>  			return;
>  		}
>  
> @@ -761,8 +894,9 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer)
>  		}
>  		converterQueue_.pop();
>  
> -		if (request)
> +		if (request) {
>  			pipe->completeRequest(request);
> +		}
>  		return;
>  	}
>  
> @@ -808,6 +942,7 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer)
>  
>  	/* Otherwise simply complete the request. */
>  	pipe->completeBuffer(request, buffer);
> +	autoProcessing(request);
>  	pipe->completeRequest(request);
>  }
>  
> @@ -823,8 +958,10 @@ void SimpleCameraData::converterOutputDone(FrameBuffer *buffer)
>  
>  	/* Complete the buffer and the request. */
>  	Request *request = buffer->request();
> -	if (pipe->completeBuffer(request, buffer))
> +	if (pipe->completeBuffer(request, buffer)) {
> +		autoProcessing(request);
>  		pipe->completeRequest(request);
> +	}
>  }
>  
>  /* Retrieve all source pads connected to a sink pad through active routes. */

-- 
Regards,

Laurent Pinchart


More information about the libcamera-devel mailing list