1 /*
2 Copyright (C) 2003 Daniel Moreno <comac@comac.darktech.org>
3 Converted for use in transcode by Tilmann Bitterberg <transcode@tibit.org>
4 Converted hqdn3d -> denoise3d and also heavily optimised for transcode
5 by Erik Slagter <erik@oldconomy.org> (GPL) 2003
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 */
21
22 #define MOD_NAME "filter_denoise3d.so"
23 #define MOD_VERSION "v1.0.6 (2003-12-20)"
24 #define MOD_CAP "High speed 3D Denoiser"
25 #define MOD_AUTHOR "Daniel Moreno, A'rpi"
26
27 #include "transcode.h"
28 #include "filter.h"
29 #include "libtc/libtc.h"
30 #include "libtc/optstr.h"
31
32 #include <math.h>
33
34 /*
35 set tabstop=4 for best layout
36
37 Changelog
38
39 1.0.3 EMS first public version
40 1.0.4 EMS added YUV422 support
41 1.0.5 EMS added RGB support
42 large cleanup
43 added arbitrary layout support
44 denoising U&V (colour) planes now actually works
45 1.0.6 EMS fixed annoying typo
46 */
47
48 #define MAX_PLANES 3
49
50 #define DEFAULT_LUMA_SPATIAL 4.0
51 #define DEFAULT_CHROMA_SPATIAL 3.0
52 #define DEFAULT_LUMA_TEMPORAL 6.0
53 #define DEFAULT_CHROMA_TEMPORAL 4.0
54
55 typedef enum { dn3d_yuv420p, dn3d_yuv422, dn3d_rgb } dn3d_fmt_t;
56 typedef enum { dn3d_planar, dn3d_packed } dn3d_basic_layout_t;
57 typedef enum { dn3d_luma, dn3d_chroma, dn3d_disabled } dn3d_plane_type_t;
58
59 typedef enum
60 {
61 dn3d_off_y420, dn3d_off_u420, dn3d_off_v420,
62 dn3d_off_y422, dn3d_off_u422, dn3d_off_v422,
63 dn3d_off_r, dn3d_off_g, dn3d_off_b
64 } dn3d_offset_t;
65
66 typedef struct
67 {
68 dn3d_plane_type_t plane_type;
69 dn3d_offset_t offset;
70 int skip;
71 int scale_x;
72 int scale_y;
73 } dn3d_single_layout_t;
74
75 typedef struct
76 {
77 int tc_fmt;
78 dn3d_fmt_t fmt;
79 dn3d_basic_layout_t layout_type;
80 dn3d_single_layout_t layout[MAX_PLANES];
81 } dn3d_layout_t;
82
83 typedef struct
84 {
85 vob_t * vob;
86 dn3d_layout_t layout_data;
87
88 struct
89 {
90 double luma_spatial;
91 double chroma_spatial;
92 double luma_temporal;
93 double chroma_temporal;
94 } parameter;
95
96 int coefficients[4][512];
97 unsigned char * lineant;
98 unsigned char * previous;
99 int prefilter;
100 int enable_luma;
101 int enable_chroma;
102
103 } dn3d_private_data_t;
104
105 /* FIXME: this uses the filter ID as an index--the ID can grow
106 * arbitrarily large, so this needs to be fixed */
107 static dn3d_private_data_t dn3d_private_data[100];
108
109 static const dn3d_layout_t dn3d_layout[] =
110 {
111 { CODEC_YUV, dn3d_yuv420p, dn3d_planar, {{ dn3d_luma, dn3d_off_y420, 1, 1, 1 }, { dn3d_chroma, dn3d_off_u420, 1, 2, 2 }, { dn3d_chroma, dn3d_off_v420, 1, 2, 2 }}},
112 { CODEC_YUV422, dn3d_yuv422, dn3d_planar, {{ dn3d_luma, dn3d_off_y422, 1, 1, 1 }, { dn3d_chroma, dn3d_off_u422, 1, 2, 1 }, { dn3d_chroma, dn3d_off_v422, 1, 2, 1 }}},
113 { CODEC_RGB, dn3d_rgb, dn3d_packed, {{ dn3d_luma, dn3d_off_r, 3, 1, 1 }, { dn3d_luma, dn3d_off_g, 3, 1, 1 }, { dn3d_luma, dn3d_off_b, 3, 1, 1 }}}
114 };
115
116 #define LowPass(prev, curr, coef) (curr + coef[prev - curr])
117
ABS(int i)118 static inline int ABS(int i)
119 {
120 return(((i) >= 0) ? i : (0 - i));
121 }
122
deNoise(unsigned char * frame,unsigned char * frameprev,unsigned char * lineant,int w,int h,int * horizontal,int * vertical,int * temporal,int offset,int skip)123 static void deNoise(unsigned char * frame, unsigned char * frameprev, unsigned char * lineant,
124 int w, int h,
125 int * horizontal, int * vertical, int * temporal,
126 int offset, int skip)
127 {
128 int x, y;
129 unsigned char pixelant;
130 unsigned char * lineantptr = lineant;
131
132 horizontal += 256;
133 vertical += 256;
134 temporal += 256;
135
136 frame += offset;
137 frameprev += offset;
138
139 // First pixel has no left nor top neighbour, only previous frame
140
141 *lineantptr = pixelant = *frame;
142 *frame = *frameprev = LowPass(*(frameprev), *lineantptr, temporal);
143 frame += skip;
144 frameprev += skip;
145 lineantptr++;
146
147 // First line has no top neighbour, only left one for each pixel and last frame
148
149 for(x = 1; x < w; x++)
150 {
151 pixelant = LowPass(pixelant, *frame, horizontal);
152 *lineantptr = pixelant;
153 *frame = *frameprev = LowPass(*(frameprev), *lineantptr, temporal);
154 frame += skip;
155 frameprev += skip;
156 lineantptr++;
157 }
158
159 for (y = 1; y < h; y++)
160 {
161 lineantptr = lineant;
162
163 // First pixel on each line doesn't have previous pixel
164
165 pixelant = *frame;
166 *lineantptr = LowPass(*lineantptr, pixelant, vertical);
167 *frame = *frameprev = LowPass(*(frameprev), *lineantptr, temporal);
168 frame += skip;
169 frameprev += skip;
170 lineantptr++;
171
172 for (x = 1; x < w; x++)
173 {
174 // The rest is normal
175
176 pixelant = LowPass(pixelant, *frame, horizontal);
177 *lineantptr = LowPass(*lineantptr, pixelant, vertical);
178 *frame = *frameprev = LowPass(*(frameprev), *lineantptr, temporal);
179 // *frame ^= 255; // debug
180 frame += skip;
181 frameprev += skip;
182 lineantptr++;
183 }
184 }
185 }
186
PrecalcCoefs(int * ct,double dist25)187 static void PrecalcCoefs(int * ct, double dist25)
188 {
189 int i;
190 double gamma, simil, c;
191
192 gamma = log(0.25) / log(1.0 - dist25 / 255.0);
193
194 for(i = -256; i <= 255; i++)
195 {
196 simil = 1.0 - (double)ABS(i) / 255.0;
197 c = pow(simil, gamma) * (double)i;
198 ct[256 + i] = (int)((c < 0) ? (c - 0.5) : (c + 0.5));
199 }
200 }
201
help_optstr(void)202 static void help_optstr(void)
203 {
204 tc_log_info(MOD_NAME, "(%s) help\n"
205 "* Overview\n"
206 " This filter aims to reduce image noise producing\n"
207 " smooth images and making still images really still\n"
208 " (This should enhance compressibility).\n"
209 "* Options\n"
210 " luma: spatial luma strength (%f)\n"
211 " chroma: spatial chroma strength (%f)\n"
212 " luma_strength: temporal luma strength (%f)\n"
213 " chroma_strength: temporal chroma strength (%f)\n"
214 " pre: run as a pre filter (0)\n"
215 , MOD_CAP,
216 DEFAULT_LUMA_SPATIAL,
217 DEFAULT_CHROMA_SPATIAL,
218 DEFAULT_LUMA_TEMPORAL,
219 DEFAULT_CHROMA_TEMPORAL);
220 }
221
tc_filter(frame_list_t * vframe_,char * options)222 int tc_filter(frame_list_t *vframe_, char * options)
223 {
224 vframe_list_t *vframe = (vframe_list_t *)vframe_;
225 int instance;
226 int tag = vframe->tag;
227 dn3d_private_data_t * pd;
228
229 if(tag & TC_AUDIO)
230 return(0);
231
232 instance = vframe->filter_id;
233 pd = &dn3d_private_data[instance];
234
235 if(tag & TC_FILTER_GET_CONFIG)
236 {
237 char buf[128];
238 optstr_filter_desc(options, MOD_NAME, MOD_CAP, MOD_VERSION, MOD_AUTHOR, "VYMOE", "2");
239
240 tc_snprintf(buf, 128, "%f", DEFAULT_LUMA_SPATIAL);
241 optstr_param(options, "luma", "spatial luma strength", "%f", buf, "0.0", "100.0" );
242
243 tc_snprintf(buf, 128, "%f", DEFAULT_CHROMA_SPATIAL);
244 optstr_param(options, "chroma", "spatial chroma strength", "%f", buf, "0.0", "100.0" );
245
246 tc_snprintf(buf, 128, "%f", DEFAULT_LUMA_TEMPORAL);
247 optstr_param(options, "luma_strength", "temporal luma strength", "%f", buf, "0.0", "100.0" );
248
249 tc_snprintf(buf, 128, "%f", DEFAULT_CHROMA_TEMPORAL);
250 optstr_param(options, "chroma_strength", "temporal chroma strength", "%f", buf, "0.0", "100.0" );
251
252 tc_snprintf(buf, 128, "%d", dn3d_private_data[instance].prefilter);
253 optstr_param(options, "pre", "run as a pre filter", "%d", buf, "0", "1" );
254 }
255
256 if(tag & TC_FILTER_INIT)
257 {
258 int format_index, plane_index, found;
259 const dn3d_layout_t * lp;
260 size_t size;
261
262 if(!(pd->vob = tc_get_vob()))
263 return(TC_IMPORT_ERROR);
264
265 pd->parameter.luma_spatial = 0;
266 pd->parameter.luma_temporal = 0;
267 pd->parameter.chroma_spatial = 0;
268 pd->parameter.chroma_temporal = 0;
269
270 if(!options)
271 {
272 tc_log_error(MOD_NAME, "options not set!");
273 return(TC_IMPORT_ERROR);
274 }
275
276 if(optstr_lookup(options, "help"))
277 {
278 help_optstr();
279 return(TC_IMPORT_ERROR);
280 }
281
282 optstr_get(options, "luma", "%lf", &pd->parameter.luma_spatial);
283 optstr_get(options, "luma_strength", "%lf", &pd->parameter.luma_temporal);
284 optstr_get(options, "chroma", "%lf", &pd->parameter.chroma_spatial);
285 optstr_get(options, "chroma_strength", "%lf", &pd->parameter.chroma_temporal);
286 optstr_get(options, "pre", "%d", &dn3d_private_data[instance].prefilter);
287
288 if((pd->parameter.luma_spatial < 0) || (pd->parameter.luma_temporal < 0))
289 pd->enable_luma = 0;
290 else
291 {
292 pd->enable_luma = 1;
293
294 if(pd->parameter.luma_spatial == 0)
295 {
296 if(pd->parameter.luma_temporal == 0)
297 {
298 pd->parameter.luma_spatial = DEFAULT_LUMA_SPATIAL;
299 pd->parameter.luma_temporal = DEFAULT_LUMA_TEMPORAL;
300 }
301 else
302 {
303 pd->parameter.luma_spatial = pd->parameter.luma_temporal * 3 / 2;
304 }
305 }
306 else
307 {
308 if(pd->parameter.luma_temporal == 0)
309 {
310 pd->parameter.luma_temporal = pd->parameter.luma_spatial * 2 / 3;
311 }
312 }
313 }
314
315 if((pd->parameter.chroma_spatial < 0) || (pd->parameter.chroma_temporal < 0))
316 pd->enable_chroma = 0;
317 else
318 {
319 pd->enable_chroma = 1;
320
321 if(pd->parameter.chroma_spatial == 0)
322 {
323 if(pd->parameter.chroma_temporal == 0)
324 {
325 pd->parameter.chroma_spatial = DEFAULT_CHROMA_SPATIAL;
326 pd->parameter.chroma_temporal = DEFAULT_CHROMA_TEMPORAL;
327 }
328 else
329 {
330 pd->parameter.chroma_spatial = pd->parameter.chroma_temporal * 3 / 2;
331 }
332 }
333 else
334 {
335 if(pd->parameter.chroma_temporal == 0)
336 {
337 pd->parameter.chroma_temporal = pd->parameter.chroma_spatial * 2 / 3;
338 }
339 }
340 }
341
342 for(format_index = 0, found = 0; format_index < (sizeof(dn3d_layout) / sizeof(*dn3d_layout)); format_index++)
343 {
344 if(pd->vob->im_v_codec == dn3d_layout[format_index].tc_fmt)
345 {
346 found = 1;
347 break;
348 }
349 }
350
351 if(!found)
352 {
353 tc_log_error(MOD_NAME, "This filter is only capable of YUV, YUV422 and RGB mode");
354 return(TC_IMPORT_ERROR);
355 }
356
357 lp = &dn3d_layout[format_index];
358 pd->layout_data = *lp;
359
360 for(plane_index = 0; plane_index < MAX_PLANES; plane_index++)
361 {
362 if((pd->layout_data.layout[plane_index].plane_type == dn3d_luma) && !pd->enable_luma)
363 pd->layout_data.layout[plane_index].plane_type = dn3d_disabled;
364
365 if((pd->layout_data.layout[plane_index].plane_type == dn3d_chroma) && !pd->enable_chroma)
366 pd->layout_data.layout[plane_index].plane_type = dn3d_disabled;
367 }
368
369 size = pd->vob->im_v_width * MAX_PLANES * sizeof(char) * 2;
370
371 pd->lineant = tc_zalloc(size);
372 if(pd->lineant == NULL)
373 tc_log_error(MOD_NAME, "Malloc failed");
374
375 size *= pd->vob->im_v_height * 2;
376
377 pd->previous = tc_zalloc(size);
378 if(pd->previous == NULL)
379 tc_log_error(MOD_NAME, "Malloc failed");
380
381 PrecalcCoefs(pd->coefficients[0], pd->parameter.luma_spatial);
382 PrecalcCoefs(pd->coefficients[1], pd->parameter.luma_temporal);
383 PrecalcCoefs(pd->coefficients[2], pd->parameter.chroma_spatial);
384 PrecalcCoefs(pd->coefficients[3], pd->parameter.chroma_temporal);
385
386 if(verbose)
387 {
388 tc_log_info(MOD_NAME, "%s %s #%d", MOD_VERSION, MOD_CAP, instance);
389 tc_log_info(MOD_NAME, "Settings luma (spatial): %.2f "
390 "luma_strength (temporal): %.2f "
391 "chroma (spatial): %.2f "
392 "chroma_strength (temporal): %.2f",
393 pd->parameter.luma_spatial,
394 pd->parameter.luma_temporal,
395 pd->parameter.chroma_spatial,
396 pd->parameter.chroma_temporal);
397 tc_log_info(MOD_NAME, "luma enabled: %s, chroma enabled: %s",
398 pd->enable_luma ? "yes" : "no",
399 pd->enable_chroma ? "yes" : "no");
400 }
401 }
402
403 if(((tag & TC_PRE_M_PROCESS && pd->prefilter) || (tag & TC_POST_M_PROCESS && !pd->prefilter)) &&
404 !(vframe->attributes & TC_FRAME_IS_SKIPPED))
405 {
406 int plane_index, coef[2];
407 int offset = 0;
408 const dn3d_single_layout_t * lp;
409
410 for(plane_index = 0; plane_index < MAX_PLANES; plane_index++)
411 {
412 lp = &pd->layout_data.layout[plane_index];
413
414 if(lp->plane_type != dn3d_disabled)
415 {
416 // if(plane_index != 2) // debug
417 // continue;
418
419 coef[0] = (lp->plane_type == dn3d_luma) ? 0 : 2;
420 coef[1] = coef[0] + 1;
421
422 switch(lp->offset)
423 {
424 case(dn3d_off_r): offset = 0; break;
425 case(dn3d_off_g): offset = 1; break;
426 case(dn3d_off_b): offset = 2; break;
427
428 case(dn3d_off_y420): offset = vframe->v_width * vframe->v_height * 0 / 4; break;
429 case(dn3d_off_u420): offset = vframe->v_width * vframe->v_height * 4 / 4; break;
430 case(dn3d_off_v420): offset = vframe->v_width * vframe->v_height * 5 / 4; break;
431
432 case(dn3d_off_y422): offset = vframe->v_width * vframe->v_height * 0 / 2; break;
433 case(dn3d_off_u422): offset = vframe->v_width * vframe->v_height * 2 / 2; break;
434 case(dn3d_off_v422): offset = vframe->v_width * vframe->v_height * 3 / 2; break;
435
436 }
437
438 deNoise(vframe->video_buf, // frame
439 pd->previous, // previous (saved) frame
440 pd->lineant, // line buffer
441 vframe->v_width / lp->scale_x, // width (pixels)
442 vframe->v_height / lp->scale_y, // height (pixels) // debug
443 pd->coefficients[coef[0]], // horizontal (spatial) strength
444 pd->coefficients[coef[0]], // vertical (spatial) strength
445 pd->coefficients[coef[1]], // temporal strength
446 offset, // offset in bytes of first relevant pixel in frame
447 lp->skip // skip this amount of bytes between two pixels
448 );
449 }
450 }
451 }
452
453 if(tag & TC_FILTER_CLOSE)
454 {
455 if(pd->previous)
456 {
457 free(pd->previous);
458 pd->previous = 0;
459 }
460
461 if(pd->lineant)
462 {
463 free(pd->lineant);
464 pd->lineant = 0;
465 }
466 }
467
468 return(0);
469 }
470