From patchwork Sun Nov 12 14:27:19 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Pavel Machek X-Patchwork-Id: 10054805 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id A335460365 for ; Sun, 12 Nov 2017 14:27:25 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 8697429842 for ; Sun, 12 Nov 2017 14:27:25 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 7836D29877; Sun, 12 Nov 2017 14:27:25 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.9 required=2.0 tests=BAYES_00,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 5FA4529842 for ; Sun, 12 Nov 2017 14:27:23 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751150AbdKLO1W (ORCPT ); Sun, 12 Nov 2017 09:27:22 -0500 Received: from atrey.karlin.mff.cuni.cz ([195.113.26.193]:44639 "EHLO atrey.karlin.mff.cuni.cz" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1750953AbdKLO1V (ORCPT ); Sun, 12 Nov 2017 09:27:21 -0500 Received: by atrey.karlin.mff.cuni.cz (Postfix, from userid 512) id D7E75823AC; Sun, 12 Nov 2017 15:27:19 +0100 (CET) Date: Sun, 12 Nov 2017 15:27:19 +0100 From: Pavel Machek To: Mauro Carvalho Chehab , hverkuil@xs4all.nl Cc: Sakari Ailus , Sakari Ailus , pali.rohar@gmail.com, sre@kernel.org, ivo.g.dimitrov.75@gmail.com, linux-media@vger.kernel.org Subject: [rfc] libv4l2: better auto-gain Message-ID: <20171112142719.GA24519@amd> MIME-Version: 1.0 Content-Disposition: inline User-Agent: Mutt/1.5.23 (2014-03-12) Sender: linux-media-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Add support for better autogain. Old code had average brightness as a target. New code has number of bright pixels as a target. Signed-off-by: Pavel Machek I see I need to implement histogram for bayer8 and rgb24. Any other comments? Best regards, Pavel diff --git a/lib/libv4lconvert/processing/autogain.c b/lib/libv4lconvert/processing/autogain.c index c6866d6..a2c69f4 100644 --- a/lib/libv4lconvert/processing/autogain.c +++ b/lib/libv4lconvert/processing/autogain.c @@ -21,6 +21,7 @@ #include #include #include +#include #include "libv4lprocessing.h" #include "libv4lprocessing-priv.h" @@ -40,179 +41,136 @@ static int autogain_active(struct v4lprocessing_data *data) return autogain; } -/* Adjust ctrl value with steps steps, while not crossing limit */ -static void autogain_adjust(struct v4l2_queryctrl *ctrl, int *value, - int steps, int limit, int accel) +#define BUCKETS 20 + +static void v4l2_histogram_bayer10(unsigned short *buf, int cdf[], const struct v4l2_format *fmt) { - int ctrl_range = (ctrl->maximum - ctrl->minimum) / ctrl->step; - - /* If we are of 3 * deadzone or more, and we have a fine grained - control, take larger steps, otherwise we take ages to get to the - right setting point. We use 256 as tripping point for determining - fine grained controls here, as avg_lum has a range of 0 - 255. */ - if (accel && abs(steps) >= 3 && ctrl_range > 256) - *value += steps * ctrl->step * (ctrl_range / 256); - /* If we are of by less then 3, but have a very finegrained control - still speed things up a bit */ - else if (accel && ctrl_range > 1024) - *value += steps * ctrl->step * (ctrl_range / 1024); - else - *value += steps * ctrl->step; - - if (steps > 0) { - if (*value > limit) - *value = limit; - } else { - if (*value < limit) - *value = limit; - } + for (int y = 0; y < fmt->fmt.pix.height; y+=19) + for (int x = 0; x < fmt->fmt.pix.width; x+=19) { + int b; + b = buf[fmt->fmt.pix.width*y + x]; + b = (b * BUCKETS)/(1024); + cdf[b]++; + } } -/* auto gain and exposure algorithm based on the knee algorithm described here: -http://ytse.tricolour.net/docs/LowLightOptimization.html */ -static int autogain_calculate_lookup_tables( - struct v4lprocessing_data *data, - unsigned char *buf, const struct v4l2_format *fmt) +static int v4l2_s_ctrl(int fd, long id, long value) { - int x, y, target, steps, avg_lum = 0; - int gain, exposure, orig_gain, orig_exposure, exposure_low; + int res; struct v4l2_control ctrl; - struct v4l2_queryctrl gainctrl, expoctrl; - const int deadzone = 6; - - ctrl.id = V4L2_CID_EXPOSURE; - expoctrl.id = V4L2_CID_EXPOSURE; - if (SYS_IOCTL(data->fd, VIDIOC_QUERYCTRL, &expoctrl) || - SYS_IOCTL(data->fd, VIDIOC_G_CTRL, &ctrl)) - return 0; - - exposure = orig_exposure = ctrl.value; - /* Determine a value below which we try to not lower the exposure, - as most exposure controls tend to jump with big steps in the low - range, causing oscilation, so we prefer to use gain when exposure - has hit this value */ - exposure_low = (expoctrl.maximum - expoctrl.minimum) / 10; - /* If we have a fine grained exposure control only avoid the last 10 steps */ - steps = exposure_low / expoctrl.step; - if (steps > 10) - steps = 10; - exposure_low = steps * expoctrl.step + expoctrl.minimum; - - ctrl.id = V4L2_CID_GAIN; - gainctrl.id = V4L2_CID_GAIN; - if (SYS_IOCTL(data->fd, VIDIOC_QUERYCTRL, &gainctrl) || - SYS_IOCTL(data->fd, VIDIOC_G_CTRL, &ctrl)) - return 0; - gain = orig_gain = ctrl.value; - - switch (fmt->fmt.pix.pixelformat) { - case V4L2_PIX_FMT_SGBRG8: - case V4L2_PIX_FMT_SGRBG8: - case V4L2_PIX_FMT_SBGGR8: - case V4L2_PIX_FMT_SRGGB8: - buf += fmt->fmt.pix.height * fmt->fmt.pix.bytesperline / 4 + - fmt->fmt.pix.width / 4; - - for (y = 0; y < fmt->fmt.pix.height / 2; y++) { - for (x = 0; x < fmt->fmt.pix.width / 2; x++) - avg_lum += *buf++; - buf += fmt->fmt.pix.bytesperline - fmt->fmt.pix.width / 2; - } - avg_lum /= fmt->fmt.pix.height * fmt->fmt.pix.width / 4; - break; - - case V4L2_PIX_FMT_RGB24: - case V4L2_PIX_FMT_BGR24: - buf += fmt->fmt.pix.height * fmt->fmt.pix.bytesperline / 4 + - fmt->fmt.pix.width * 3 / 4; - - for (y = 0; y < fmt->fmt.pix.height / 2; y++) { - for (x = 0; x < fmt->fmt.pix.width / 2; x++) { - avg_lum += *buf++; - avg_lum += *buf++; - avg_lum += *buf++; - } - buf += fmt->fmt.pix.bytesperline - fmt->fmt.pix.width * 3 / 2; - } - avg_lum /= fmt->fmt.pix.height * fmt->fmt.pix.width * 3 / 4; - break; - } + ctrl.id = id; + ctrl.value = value; + /* FIXME: we'd like to do v4l2_ioctl here, but headers + prevent that */ + res = SYS_IOCTL(fd, VIDIOC_S_CTRL, &ctrl); + if (res < 0) + printf("Set control %lx %ld failed\n", id, value); + return res; +} - /* If we are off a multiple of deadzone, do multiple steps to reach the - desired lumination fast (with the risc of a slight overshoot) */ - target = v4lcontrol_get_ctrl(data->control, V4LCONTROL_AUTOGAIN_TARGET); - steps = (target - avg_lum) / deadzone; - - /* If we were decreasing and are now increasing, or vica versa, half the - number of steps to avoid overshooting and oscilating */ - if ((steps > 0 && data->last_gain_correction < 0) || - (steps < 0 && data->last_gain_correction > 0)) - steps /= 2; - - if (steps == 0) - return 0; /* Nothing to do */ - - if (steps < 0) { - if (exposure > expoctrl.default_value) - autogain_adjust(&expoctrl, &exposure, steps, - expoctrl.default_value, 1); - else if (gain > gainctrl.default_value) - autogain_adjust(&gainctrl, &gain, steps, - gainctrl.default_value, 1); - else if (exposure > exposure_low) - autogain_adjust(&expoctrl, &exposure, steps, - exposure_low, 1); - else if (gain > gainctrl.minimum) - autogain_adjust(&gainctrl, &gain, steps, - gainctrl.minimum, 1); - else if (exposure > expoctrl.minimum) - autogain_adjust(&expoctrl, &exposure, steps, - expoctrl.minimum, 0); - else - steps = 0; - } else { - if (exposure < exposure_low) - autogain_adjust(&expoctrl, &exposure, steps, - exposure_low, 0); - else if (gain < gainctrl.default_value) - autogain_adjust(&gainctrl, &gain, steps, - gainctrl.default_value, 1); - else if (exposure < expoctrl.default_value) - autogain_adjust(&expoctrl, &exposure, steps, - expoctrl.default_value, 1); - else if (gain < gainctrl.maximum) - autogain_adjust(&gainctrl, &gain, steps, - gainctrl.maximum, 1); - else if (exposure < expoctrl.maximum) - autogain_adjust(&expoctrl, &exposure, steps, - expoctrl.maximum, 1); - else - steps = 0; +static int v4l2_set_exposure(struct v4lprocessing_data *data, double exposure) +{ + double exp, gain; /* microseconds */ + int exp_, gain_; + int fd = data->fd; + + gain = 1; + exp = exposure / gain; + if (exp > 10000) { + exp = 10000; + gain = exposure / exp; } - - if (steps) { - data->last_gain_correction = steps; - /* We are still settling down, force the next update sooner. Note we - skip the next frame as that is still captured with the old settings, - and another one just to be sure (because if we re-adjust based - on the old settings we might overshoot). */ - data->lookup_table_update_counter = V4L2PROCESSING_UPDATE_RATE - 2; + if (gain > 16) { + gain = 16; + exp = exposure / gain; } - if (gain != orig_gain) { - ctrl.id = V4L2_CID_GAIN; - ctrl.value = gain; - SYS_IOCTL(data->fd, VIDIOC_S_CTRL, &ctrl); + exp_ = exp; + gain_ = 10*log(gain)/log(2); + printf("Exposure %f %d, gain %f %d\n", exp, exp_, gain, gain_); + + /* gain | ISO | gain_ + * 1. | 100 | 0 + * 2. | 200 | 10 + * ... + * 16. | 1600 | 40 + */ + + if (v4l2_s_ctrl(fd, V4L2_CID_GAIN, gain_) < 0) { + printf("Could not set gain\n"); } - if (exposure != orig_exposure) { - ctrl.id = V4L2_CID_EXPOSURE; - ctrl.value = exposure; - SYS_IOCTL(data->fd, VIDIOC_S_CTRL, &ctrl); + if (v4l2_s_ctrl(fd, V4L2_CID_EXPOSURE_ABSOLUTE, exp_) < 0) { + printf("Could not set exposure\n"); } + return 0; +} + +struct exposure_data { + double exposure; +}; +static int autogain_calculate_lookup_tables_exp( + struct v4lprocessing_data *data, + unsigned char *buf, const struct v4l2_format *fmt) +{ + int cdf[BUCKETS] = { 0, }; + static struct exposure_data e_data; + static struct exposure_data *exp = &e_data; + + v4l2_histogram_bayer10((void *) buf, cdf, fmt); + + for (int i=1; i maxSaturatedPixels) { + /* first don't let things saturate too much */ + adjustment = 1.0f - ((float)(saturatedPixels - maxSaturatedPixels))/cdf[b-1]; + } else if (brightPixels < (targetBrightPixels - (saturatedPixels * 4))) { + /* increase brightness to try and hit the desired + number of well exposed pixels + */ + int l = b-6; + while (brightPixels < targetBrightPixels && l > 0) { + brightPixels += cdf[l]; + brightPixels -= cdf[l-1]; + l--; + } + + adjustment = ((float) (b-6+1))/(l+1); + } + /* else we're not oversaturated, and we have enough bright pixels. + Do nothing. + */ + + float limit = 4; + if (adjustment > limit) { adjustment = limit; } + if (adjustment < 1/limit) { adjustment = 1/limit; } + + exp->exposure *= adjustment; + if (exp->exposure < 1) + exp->exposure = 1; + + float elimit = 64000000; + if (exp->exposure > elimit) + exp->exposure = elimit; + + if (adjustment != 1.) + printf("AutoExposure: adjustment: %f exposure %f\n", + adjustment, exp->exposure); + + v4l2_set_exposure(data, exp->exposure); return 0; } struct v4lprocessing_filter autogain_filter = { - autogain_active, autogain_calculate_lookup_tables + autogain_active, autogain_calculate_lookup_tables_exp }; +