summaryrefslogtreecommitdiff
path: root/camlibs/stv0680/demosaic_sharpen.c
blob: 30848f23a78503c74c42788e899c14e46e4a2368 (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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
/** demosaic_sharpen.c
 * when demosaicing the unbayered image,
 * don't just use bilinear interpolation, but weigh
 * the to be guessed values according to the differences
 * of the known value.
 * Of course, smaller differences mean higher weights.
 *
 * Copyright Kurt Garloff <garloff@suse.de>, 2002/01/15
 * License: GNU GPL
 *
 * Note: Interpolation techniques more intelligent than
 * bilinear inerpolation have been subject to investigations.
 * Those two links came from Jérôme Fenal:
 * http://ise.stanford.edu/class/psych221/99/dixiedog/vision.htm
 * http://www-ise.stanford.edu/~tingchen/main.htm
 *
 * From the evaluation, it seems that we should strive to do something like
 * "Linear Interpolation with Laplacian 2nd order color correction terms I".
 *
 * Here; I went my own way.
 * The reason for this is twofold:
 * - Avoid all possible patent issues
 *   (If I would be American, I would probably want to patent this algo)
 * - Be conservative: By using an interpolation method (with weights bound
 *   below 1), we avoid the risk to get artefacts, like e.g. seen in the
 *   smooth hue algorithms, as our interpolated values are always in between
 *   those from the neighbours, just a little closer to the ones we think
 *   fits better.
 *
 * Our algorithm:
 * Trying to predict the known value based on the next neighbours with
 * the same colour component will yield weights. To achieve this:
 * - Measure the difference |dv| of the known colour component
 * - Choose a function f(|dv|) which prefers points with smaller |dv|
 * - Function should be monotonic and ]0;1[
 * - Sum of weights must be 1, of course
 *
 * We choose f(|dv|) = N / (ALPHA + |dv|)
 * and scale with the reciprocal total sum, so we fulfill the conditions.
 * The algorithm is integer only (unless you enable DEBUG)
 * and therefore suitable for FPU-less machines and/or the kernel.
 *
 * I've chosen ALPHA = 2 and the results look really good.
 *
 * ToDo:
 * - A similar algorithm in HSI space might be slightly better.
 * - Different weighing functions might do better
 * - Do rigorous performance analysis (quality and computation cost)
 *   in comparison to other algos as in papers cited above.
 * - There's the bilinear interpolation included for reference
 *   (debugging purposes). Use it or get rid of it for slightly better
 *   performance.
 *
 * I've implemented this algo here as a testbed. It's only tested for
 * BAYER_TILE_GBRG_INTERLACED, though it's been designed to be general
 * for all BAYERs in gphoto2. (Hence all those tables ...)
 * It should be moved to the gphoto2 infrastructure to help all
 * cameras, not just mine. It includes the demosaicing, so it should
 * be merged with the gp_bayer_decode (or the bilinear demosaicing
 * could be removed from the latter) to avoid double work.
 *
 * History:
 * 2001-01-15, 0.90, KG,
 *		working for inner points (2,2)-(width-3,height-3)
 * 2001-01-15, 1.00, KG,
 *		handle boundary points
 *
 */

#include "config.h"
#include <stdlib.h>
#include "demosaic_sharpen.h"

/* we define bayer as
 * +---> x
 * | 0 1
 * v 2 3
 * y
*/

typedef enum {
	RED = 0, GREEN = 1, BLUE = 2
} col;

/* Don't get confused reading this code; there's lots of
 * indirection through the tables to avoid branches in the code;
 * maybe I love long pipelines too much.
 * If I look at the code long enough, I get confused myself.
 * The boundary special cases unfortunately do introduce some extra
 * branching. (KG)
 */

/* relative postition */
typedef struct _off {
	signed char dx, dy;
} off;

typedef enum {
	NB_DIAG = 0, NB_TLRB, NB_LR, NB_TB, NB_TLRB2
} nb_pat;
/* locations of neighbour points with the same colour */
typedef struct _neighbours {
	unsigned char num;
	off nb_pts[4];
} neighbours;

/* possible locations */
static const neighbours n_pos[8] = {
	{	/* NB_DIAG */
		4, {
			{-1,-1},
			{ 1,-1},
			{-1, 1},
			{ 1, 1}
		}
	},{	/* NB_TLRB */
		4, {
			{ 0,-1},
			{-1, 0},
			{ 1, 0},
			{ 0, 1}
		}
	},{	/* NB_LR */
		2, {
			{-1, 0},
			{ 1, 0},
			{ 0, 0},
			{ 0, 0}
		}
	},{	/* NB_TB */
		2, {
			{ 0,-1},
			{ 0, 1},
			{ 0, 0},
			{ 0, 0}
		}
	},{	/* NB_TLRB2 */
		4, {
			{ 0,-2},
			{-2, 0},
			{ 2, 0},
			{ 0, 2},
		}
	}
};

typedef struct _t_coeff {
	unsigned char cf[4][4];
	unsigned char num;
} t_coeff;

typedef enum {
	DIAG_TO_LR = 0, DIAG_TO_TB, TLRB2_TO_DIAG, TLRB2_TO_TLRB, PATCONV_NONE
} patconv;


/* Transfer matrix pattern to pattern */
static const t_coeff pat_to_pat[4] = {
	{	/* DIAG_TO_LR */
		{
			{2, 0, 2, 0},
			{0, 2, 0, 2},
			{0, 0, 0, 0},
			{0, 0, 0, 0}
		}, 2
	},{	/* DIAG_TO_TB */
		{
			{ 2, 2, 0, 0},
			{ 0, 0, 2, 2},
			{ 0, 0, 0, 0},
			{ 0, 0, 0, 0},
		}, 2
	},{	/* TLRB2_TO_DIAG */
		{
			{1, 1, 0, 0},
			{1, 0, 1, 0},
			{0, 1, 0, 1},
			{0, 0, 1, 1}
		}, 4
	},{	/* TLRB2_TO_TLRB (trivial) */
		{
			{2, 0, 0, 0},
			{0, 2, 0, 0},
			{0, 0, 2, 0},
			{0, 0, 0, 2}
		}, 4
	}
};

static const patconv pconvmap[5][5] = {
	{ PATCONV_NONE, PATCONV_NONE, DIAG_TO_LR, DIAG_TO_TB, PATCONV_NONE },
	{ PATCONV_NONE, PATCONV_NONE, PATCONV_NONE, PATCONV_NONE, PATCONV_NONE },
	{ PATCONV_NONE, PATCONV_NONE, PATCONV_NONE, PATCONV_NONE, PATCONV_NONE },
	{ PATCONV_NONE, PATCONV_NONE, PATCONV_NONE, PATCONV_NONE, PATCONV_NONE },
	{ TLRB2_TO_DIAG, TLRB2_TO_TLRB, PATCONV_NONE, PATCONV_NONE, PATCONV_NONE }
};


/* Next mapping: col of pixel (0,1,2 = RGB &
 * index into n_pos for own, own+1, own+2 */
typedef struct _bayer_desc {
	col colour;
	nb_pat idx_pts[3];
} bayer_desc;

/* T = Bayer Tile, P = Bayer point no
 *                T  P                */
static const bayer_desc bayers[4][4] = {
	{	/* TILE_RGGB */
		{ RED,   {NB_TLRB2, NB_TLRB, NB_DIAG} },
		{ GREEN, {NB_DIAG, NB_TB, NB_LR} },
		{ GREEN, {NB_DIAG, NB_LR, NB_TB} },
		{ BLUE,  {NB_TLRB2, NB_DIAG, NB_TLRB} },
	},{	/* TILE_GRBG */
		{ GREEN, {NB_DIAG, NB_TB, NB_LR} },
		{ RED,   {NB_TLRB2, NB_TLRB, NB_DIAG} },
		{ BLUE,  {NB_TLRB2, NB_DIAG, NB_TLRB} },
		{ GREEN, {NB_DIAG, NB_LR, NB_TB} },
	},{	/* TILE_BGGR */
		{ BLUE,  {NB_TLRB2, NB_DIAG, NB_TLRB} },
		{ GREEN, {NB_DIAG, NB_LR, NB_TB} },
		{ GREEN, {NB_DIAG, NB_TB, NB_LR} },
		{ RED,   {NB_TLRB2, NB_TLRB, NB_DIAG} },
	},{	/* TILE_GBRG */
		{ GREEN, {NB_DIAG, NB_LR, NB_TB} },
		{ BLUE,  {NB_TLRB2, NB_DIAG, NB_TLRB} },
		{ RED,   {NB_TLRB2, NB_TLRB, NB_DIAG} },
		{ GREEN, {NB_DIAG, NB_TB, NB_LR} }
	}
};

/* Use integer arithmetic. Accuracy is 10^-6, which is good enough */
#define SHIFT 20
static inline int weight (const unsigned char dx, const int alpha)
{
	return (1<<SHIFT)/(alpha + dx);
}

/* alpha controls the strength of the weighting; 1 = strongest, 64 = weak */
void demosaic_sharpen (const int width, const int height,
		       const unsigned char * const src_region,
		       unsigned char * const dest_region,
		       const int alpha, const BayerTile bt)
{
	const unsigned char* src_ptr = src_region;
	unsigned char* dst_ptr = dest_region;
	const bayer_desc *bay_des = bayers [bt & 3]; /* Don't care about interlace */
	int x, y;

	for (y = 0; y < height; y++) {
		for (x = 0; x < width; x++, src_ptr += 3, dst_ptr += 3) {
			/* 3 2 */
			/* 1 0 */
			const unsigned char bayer = (1^(x&1)) + ((1^(y&1))<<1);
			const col colour = bay_des[bayer].colour;
			/* nb_pat[0] is our own pattern */
			const nb_pat * const nbpts = bay_des[bayer].idx_pts;
			/* less strong weighting for TLRB2 pattern */
			const int myalpha = (*nbpts == NB_TLRB2? (alpha << 1): alpha);
			const unsigned char colval = src_ptr[colour];
			int weights[4]; int sum_weights = 0.0;
			patconv pconv;
			/* Calc coeffs for prediction */
			int nbs; col ncol; int othcol; int i;
			int skno; int nsumw;
			int predcol = 0; /*  Only for DEBUG */
			/* DPRINTF("(%i,%i)(%p): bay %i, col %i, pat %i, val %i\n", x, y, src_ptr, bayer, colour, nbpts[0], colval);*/
			/* Copy own colour */
			dst_ptr[colour] = colval;
			/* Now calc weights */
			for (nbs = 0; nbs < 4; nbs++) {
				const off offset = n_pos[nbpts[0]].nb_pts[nbs];
				const int nx = x + offset.dx;
				const int ny = y + offset.dy;
				const signed long addr_off = 3 * (offset.dx + width * offset.dy);
				unsigned char thisval = colval; int coeff = 0;
				if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
					thisval = src_ptr[addr_off+colour];
					coeff = weight (abs ((int)colval - thisval), myalpha);
				} else if (nbpts[0] == NB_TLRB2 && x > 0 && x < width-1 && y > 0 && y < height-1) {
					coeff = weight (128, myalpha); /* assign some small weight */
				}
				/*DPRINTF(" (%i,%i)(%p): val %i, diff %i, weight %i\n", nx, ny, src_ptr+addr_off, thisval, abs ((int)colval - thisval), coeff);*/
				predcol += thisval * coeff;
				weights[nbs] = coeff;
				sum_weights += coeff;
			};
#ifdef DEBUG
			printf(" Coeffs:");
			for (nbs = 0; nbs < 4; nbs++)
				printf (" %6.4f", (double)weights[nbs]/sum_weights);
			printf (" -> pred %i\n", predcol/sum_weights);
#endif
			/* Now calculate other colours */
			ncol = (colour+1)%3;
			pconv = pconvmap[nbpts[0]][nbpts[1]];
			if (pconv == PATCONV_NONE)
				abort ();
			othcol = 0; predcol = 0; nsumw = 0; skno = 0;
			/*DPRINTF(" Col %i: pat %i pconv %i\n", ncol, nbpts[1], pconv);*/
			for (nbs = 0; nbs < n_pos[nbpts[1]].num; nbs++) {
				off offset = n_pos[nbpts[1]].nb_pts[nbs];
				const int nx = x + offset.dx;
				const int ny = y + offset.dy;
				const signed long addr_off = 3 * (offset.dx + width * offset.dy);
				int eff_weight = 0; unsigned char thisval;
				for (i = 0; i < 4; i++)
					eff_weight += pat_to_pat[pconv].cf[nbs][i] * weights[i];
				if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
					thisval = src_ptr[addr_off+ncol];
					nsumw += eff_weight;
					/*DPRINTF("  (%i,%i): val %i, eff_w %6.4f\n", nx, ny, thisval, (double)(eff_weight>>1)/sum_weights);*/
					othcol  += thisval * eff_weight;
					predcol += thisval;
				} else {
					skno++;
				}
			};
			dst_ptr[ncol] = othcol/nsumw;
			/*DPRINTF( " -> val %i (bilin: %i)\n", dst_ptr[ncol], predcol/(n_pos[nbpts[1]].num-skno));*/
			/* Third colour */
			ncol = (colour+2)%3;
			pconv = pconvmap[nbpts[0]][nbpts[2]];
			if (pconv == PATCONV_NONE)
				abort ();
			othcol = 0; predcol = 0; nsumw = 0; skno = 0;
			/*DPRINTF(" Col %i: pat %i pconv %i\n", ncol, nbpts[2], pconv);*/
			for (nbs = 0; nbs < n_pos[nbpts[2]].num; nbs++) {
				off offset = n_pos[nbpts[2]].nb_pts[nbs];
				const int nx = x + offset.dx;
				const int ny = y + offset.dy;
				const signed long addr_off = 3 * (offset.dx + width * offset.dy);
				int eff_weight = 0; unsigned char thisval;
				for (i = 0; i < 4; i++)
					eff_weight += pat_to_pat[pconv].cf[nbs][i] * weights[i];
				if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
					thisval = src_ptr[addr_off+ncol];
					nsumw += eff_weight;
					/*DPRINTF("  (%i,%i): val %i, eff_w %6.4f\n", nx, ny, thisval, (double)(eff_weight>>1)/sum_weights);*/
					othcol  += thisval * eff_weight;
					predcol += thisval;
				} else {
					skno++;
				}
			};
			dst_ptr[ncol] = othcol/nsumw;
			/*DPRINTF( " -> val %i (bilin: %i)\n", dst_ptr[ncol], predcol/(n_pos[nbpts[1]].num-skno));*/
		}
	}
}