summaryrefslogtreecommitdiff
path: root/src/ipa/raspberrypi/controller/rpi/alsc.hpp
blob: 13d1ba5439fb65f8f9b9f3d17733dfbc626c3b96 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
/* SPDX-License-Identifier: BSD-2-Clause */
/*
 * Copyright (C) 2019, Raspberry Pi (Trading) Limited
 *
 * alsc.hpp - ALSC (auto lens shading correction) control algorithm
 */
#pragma once

#include <mutex>
#include <condition_variable>
#include <thread>

#include "../algorithm.hpp"
#include "../alsc_status.h"

namespace RPiController {

// Algorithm to generate automagic LSC (Lens Shading Correction) tables.

struct AlscCalibration {
	double ct;
	double table[ALSC_CELLS_X * ALSC_CELLS_Y];
};

struct AlscConfig {
	// Only repeat the ALSC calculation every "this many" frames
	uint16_t frame_period;
	// number of initial frames for which speed taken as 1.0 (maximum)
	uint16_t startup_frames;
	// IIR filter speed applied to algorithm results
	double speed;
	double sigma_Cr;
	double sigma_Cb;
	double min_count;
	uint16_t min_G;
	double omega;
	uint32_t n_iter;
	double luminance_lut[ALSC_CELLS_X * ALSC_CELLS_Y];
	double luminance_strength;
	std::vector<AlscCalibration> calibrations_Cr;
	std::vector<AlscCalibration> calibrations_Cb;
	double default_ct; // colour temperature if no metadata found
	double threshold; // iteration termination threshold
};

class Alsc : public Algorithm
{
public:
	Alsc(Controller *controller = NULL);
	~Alsc();
	char const *Name() const override;
	void Initialise() override;
	void SwitchMode(CameraMode const &camera_mode, Metadata *metadata) override;
	void Read(boost::property_tree::ptree const &params) override;
	void Prepare(Metadata *image_metadata) override;
	void Process(StatisticsPtr &stats, Metadata *image_metadata) override;

private:
	// configuration is read-only, and available to both threads
	AlscConfig config_;
	bool first_time_;
	CameraMode camera_mode_;
	double luminance_table_[ALSC_CELLS_X * ALSC_CELLS_Y];
	std::thread async_thread_;
	void asyncFunc(); // asynchronous thread function
	std::mutex mutex_;
	// condvar for async thread to wait on
	std::condition_variable async_signal_;
	// condvar for synchronous thread to wait on
	std::condition_variable sync_signal_;
	// for sync thread to check  if async thread finished (requires mutex)
	bool async_finished_;
	// for async thread to check if it's been told to run (requires mutex)
	bool async_start_;
	// for async thread to check if it's been told to quit (requires mutex)
	bool async_abort_;

	// The following are only for the synchronous thread to use:
	// for sync thread to note its has asked async thread to run
	bool async_started_;
	// counts up to frame_period before restarting the async thread
	int frame_phase_;
	// counts up to startup_frames
	int frame_count_;
	// counts up to startup_frames for Process method
	int frame_count2_;
	double sync_results_[3][ALSC_CELLS_Y][ALSC_CELLS_X];
	double prev_sync_results_[3][ALSC_CELLS_Y][ALSC_CELLS_X];
	void waitForAysncThread();
	// The following are for the asynchronous thread to use, though the main
	// thread can set/reset them if the async thread is known to be idle:
	void restartAsync(StatisticsPtr &stats, Metadata *image_metadata);
	// copy out the results from the async thread so that it can be restarted
	void fetchAsyncResults();
	double ct_;
	bcm2835_isp_stats_region statistics_[ALSC_CELLS_Y * ALSC_CELLS_X];
	double async_results_[3][ALSC_CELLS_Y][ALSC_CELLS_X];
	double async_lambda_r_[ALSC_CELLS_X * ALSC_CELLS_Y];
	double async_lambda_b_[ALSC_CELLS_X * ALSC_CELLS_Y];
	void doAlsc();
	double lambda_r_[ALSC_CELLS_X * ALSC_CELLS_Y];
	double lambda_b_[ALSC_CELLS_X * ALSC_CELLS_Y];
};

} // namespace RPiController
= 0; i < CONTRAST_NUM_POINTS - 1; i++) { int x = i < 16 ? i * 1024 : (i < 24 ? (i - 16) * 2048 + 16384 : (i - 24) * 4096 + 32768); status.points[i].x = x; status.points[i].y = std::min(65535.0, gamma_curve.Eval(x)); } status.points[CONTRAST_NUM_POINTS - 1].x = 65535; status.points[CONTRAST_NUM_POINTS - 1].y = 65535; } void Contrast::Initialise() { // Fill in some default values as Prepare will run before Process gets // called. fill_in_status(status_, brightness_, contrast_, config_.gamma_curve); } void Contrast::Prepare(Metadata *image_metadata) { std::unique_lock<std::mutex> lock(mutex_); image_metadata->Set("contrast.status", status_); } Pwl compute_stretch_curve(Histogram const &histogram, ContrastConfig const &config) { Pwl enhance; enhance.Append(0, 0); // If the start of the histogram is rather empty, try to pull it down a // bit. double hist_lo = histogram.Quantile(config.lo_histogram) * (65536 / NUM_HISTOGRAM_BINS); double level_lo = config.lo_level * 65536; LOG(RPiContrast, Debug) << "Move histogram point " << hist_lo << " to " << level_lo; hist_lo = std::max( level_lo, std::min(65535.0, std::min(hist_lo, level_lo + config.lo_max))); LOG(RPiContrast, Debug) << "Final values " << hist_lo << " -> " << level_lo; enhance.Append(hist_lo, level_lo); // Keep the mid-point (median) in the same place, though, to limit the // apparent amount of global brightness shift. double mid = histogram.Quantile(0.5) * (65536 / NUM_HISTOGRAM_BINS); enhance.Append(mid, mid); // If the top to the histogram is empty, try to pull the pixel values // there up. double hist_hi = histogram.Quantile(config.hi_histogram) * (65536 / NUM_HISTOGRAM_BINS); double level_hi = config.hi_level * 65536; LOG(RPiContrast, Debug) << "Move histogram point " << hist_hi << " to " << level_hi; hist_hi = std::min( level_hi, std::max(0.0, std::max(hist_hi, level_hi - config.hi_max))); LOG(RPiContrast, Debug) << "Final values " << hist_hi << " -> " << level_hi; enhance.Append(hist_hi, level_hi); enhance.Append(65535, 65535); return enhance; } Pwl apply_manual_contrast(Pwl const &gamma_curve, double brightness, double contrast) { Pwl new_gamma_curve; LOG(RPiContrast, Debug) << "Manual brightness " << brightness << " contrast " << contrast; gamma_curve.Map([&](double x, double y) { new_gamma_curve.Append( x, std::max(0.0, std::min(65535.0, (y - 32768) * contrast + 32768 + brightness))); }); return new_gamma_curve; } void Contrast::Process(StatisticsPtr &stats, [[maybe_unused]] Metadata *image_metadata) { Histogram histogram(stats->hist[0].g_hist, NUM_HISTOGRAM_BINS); // We look at the histogram and adjust the gamma curve in the following // ways: 1. Adjust the gamma curve so as to pull the start of the // histogram down, and possibly push the end up. Pwl gamma_curve = config_.gamma_curve; if (config_.ce_enable) { if (config_.lo_max != 0 || config_.hi_max != 0) gamma_curve = compute_stretch_curve(histogram, config_) .Compose(gamma_curve); // We could apply other adjustments (e.g. partial equalisation) // based on the histogram...? } // 2. Finally apply any manually selected brightness/contrast // adjustment. if (brightness_ != 0 || contrast_ != 1.0) gamma_curve = apply_manual_contrast(gamma_curve, brightness_, contrast_); // And fill in the status for output. Use more points towards the bottom // of the curve. ContrastStatus status; fill_in_status(status, brightness_, contrast_, gamma_curve); { std::unique_lock<std::mutex> lock(mutex_); status_ = status; } } // Register algorithm with the system. static Algorithm *Create(Controller *controller) { return (Algorithm *)new Contrast(controller); } static RegisterAlgorithm reg(NAME, &Create);