From: Pavel Machek <pavel@ucw.cz>
To: Mauro Carvalho Chehab <mchehab@s-opensource.com>, hverkuil@xs4all.nl
Cc: Sakari Ailus <sakari.ailus@iki.fi>,
Sakari Ailus <sakari.ailus@linux.intel.com>,
pali.rohar@gmail.com, sre@kernel.org,
ivo.g.dimitrov.75@gmail.com, linux-media@vger.kernel.org
Subject: [rfc] libv4l2: better auto-gain
Date: Sun, 12 Nov 2017 15:27:19 +0100 [thread overview]
Message-ID: <20171112142719.GA24519@amd> (raw)
[-- Attachment #1: Type: text/plain, Size: 10700 bytes --]
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 <pavel@ucw.cz>
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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
+#include <math.h>
#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<BUCKETS; i++)
+ cdf[i] += cdf[i-1];
+
+ int b = BUCKETS;
+ int brightPixels = cdf[b-1] - cdf[b-8];
+ int targetBrightPixels = cdf[b-1]/50;
+ int maxSaturatedPixels = cdf[b-1]/200;
+ int saturatedPixels = cdf[b-1] - cdf[b-2];
+ /* how much should I change brightness by */
+ float adjustment = 1.0f;
+
+ if (saturatedPixels > 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
};
+
--
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]
reply other threads:[~2017-11-12 14:27 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20171112142719.GA24519@amd \
--to=pavel@ucw.cz \
--cc=hverkuil@xs4all.nl \
--cc=ivo.g.dimitrov.75@gmail.com \
--cc=linux-media@vger.kernel.org \
--cc=mchehab@s-opensource.com \
--cc=pali.rohar@gmail.com \
--cc=sakari.ailus@iki.fi \
--cc=sakari.ailus@linux.intel.com \
--cc=sre@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.