summaryrefslogtreecommitdiffstats
path: root/libhb/denoise.c
diff options
context:
space:
mode:
Diffstat (limited to 'libhb/denoise.c')
-rw-r--r--libhb/denoise.c463
1 files changed, 463 insertions, 0 deletions
diff --git a/libhb/denoise.c b/libhb/denoise.c
new file mode 100644
index 000000000..5022d4721
--- /dev/null
+++ b/libhb/denoise.c
@@ -0,0 +1,463 @@
+/*
+ Copyright (C) 2003 Daniel Moreno <[email protected]>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#include "hb.h"
+#include "ffmpeg/avcodec.h"
+#include "mpeg2dec/mpeg2.h"
+
+#define HQDN3D_SPATIAL_LUMA_DEFAULT 4.0f
+#define HQDN3D_SPATIAL_CHROMA_DEFAULT 3.0f
+#define HQDN3D_TEMPORAL_LUMA_DEFAULT 6.0f
+
+#define ABS(A) ( (A) > 0 ? (A) : -(A) )
+
+struct hb_filter_private_s
+{
+ int pix_fmt;
+ int width[3];
+ int height[3];
+
+ int hqdn3d_coef[4][512*16];
+ unsigned int * hqdn3d_line;
+ unsigned short * hqdn3d_frame[3];
+
+ AVPicture pic_in;
+ AVPicture pic_out;
+ hb_buffer_t * buf_out;
+};
+
+hb_filter_private_t * hb_denoise_init( int pix_fmt,
+ int width,
+ int height,
+ char * settings );
+
+int hb_denoise_work( const hb_buffer_t * buf_in,
+ hb_buffer_t ** buf_out,
+ int pix_fmt,
+ int width,
+ int height,
+ hb_filter_private_t * pv );
+
+void hb_denoise_close( hb_filter_private_t * pv );
+
+hb_filter_object_t hb_filter_denoise =
+{
+ FILTER_DENOISE,
+ "Denoise (hqdn3d)",
+ NULL,
+ hb_denoise_init,
+ hb_denoise_work,
+ hb_denoise_close,
+};
+
+static void hqdn3d_precalc_coef( int * ct,
+ double dist25 )
+{
+ int i;
+ double gamma, simil, c;
+
+ gamma = log( 0.25 ) / log( 1.0 - dist25/255.0 - 0.00001 );
+
+ for( i = -255*16; i <= 255*16; i++ )
+ {
+ simil = 1.0 - ABS(i) / (16*255.0);
+ c = pow( simil, gamma ) * 65536.0 * (double)i / 16.0;
+ ct[16*256+i] = (c<0) ? (c-0.5) : (c+0.5);
+ }
+
+ ct[0] = (dist25 != 0);
+}
+
+static inline unsigned int hqdn3d_lowpass_mul( unsigned int prev_mul,
+ unsigned int curr_mul,
+ int * coef )
+{
+ int diff_mul = prev_mul - curr_mul;
+ int d = ((diff_mul+0x10007FF)>>12);
+ return curr_mul + coef[d];
+}
+
+static void hqdn3d_denoise_temporal( unsigned char * frame_src,
+ unsigned char * frame_dst,
+ unsigned short * frame_ant,
+ int w, int h,
+ int * temporal)
+{
+ int x, y;
+ unsigned int pixel_dst;
+
+ for( y = 0; y < h; y++ )
+ {
+ for( x = 0; x < w; x++ )
+ {
+ pixel_dst = hqdn3d_lowpass_mul( frame_ant[x]<<8,
+ frame_src[x]<<16,
+ temporal );
+
+ frame_ant[x] = ((pixel_dst+0x1000007F)>>8);
+ frame_dst[x] = ((pixel_dst+0x10007FFF)>>16);
+ }
+
+ frame_src += w;
+ frame_dst += w;
+ frame_ant += w;
+ }
+}
+
+static void hqdn3d_denoise_spatial( unsigned char * frame_src,
+ unsigned char * frame_dst,
+ unsigned int * line_ant,
+ int w, int h,
+ int * horizontal,
+ int * vertical )
+{
+ int x, y;
+ int line_offset_src = 0, line_offset_dst = 0;
+ unsigned int pixel_ant;
+ unsigned int pixel_dst;
+
+ /* First pixel has no left nor top neighbor. */
+ pixel_dst = line_ant[0] = pixel_ant = frame_src[0]<<16;
+ frame_dst[0] = ((pixel_dst+0x10007FFF)>>16);
+
+ /* First line has no top neighbor, only left. */
+ for( x = 1; x < w; x++ )
+ {
+ pixel_dst = line_ant[x] = hqdn3d_lowpass_mul(pixel_ant,
+ frame_src[x]<<16,
+ horizontal);
+
+ frame_dst[x] = ((pixel_dst+0x10007FFF)>>16);
+ }
+
+ for( y = 1; y < h; y++ )
+ {
+ unsigned int pixel_ant;
+ line_offset_src += w, line_offset_dst += w;
+
+ /* First pixel on each line doesn't have previous pixel */
+ pixel_ant = frame_src[line_offset_src]<<16;
+
+ pixel_dst = line_ant[0] = hqdn3d_lowpass_mul( line_ant[0],
+ pixel_ant,
+ vertical);
+
+ frame_dst[line_offset_dst] = ((pixel_dst+0x10007FFF)>>16);
+
+ /* The rest of the pixels in the line are normal */
+ for( x = 1; x < w; x++ )
+ {
+ unsigned int pixel_dst;
+
+ pixel_ant = hqdn3d_lowpass_mul( pixel_ant,
+ frame_src[line_offset_src+x]<<16,
+ horizontal );
+ pixel_dst = line_ant[x] = hqdn3d_lowpass_mul( line_ant[x],
+ pixel_ant,
+ vertical );
+
+ frame_dst[line_offset_dst+x]= ((pixel_dst+0x10007FFF)>>16);
+ }
+ }
+}
+
+static void hqdn3d_denoise( unsigned char * frame_src,
+ unsigned char * frame_dst,
+ unsigned int * line_ant,
+ unsigned short ** frame_ant_ptr,
+ int w,
+ int h,
+ int * horizontal,
+ int * vertical,
+ int * temporal)
+{
+ int x, y;
+ int line_offset_src = 0, line_offset_dst = 0;
+ unsigned int pixel_ant;
+ unsigned int pixel_dst;
+ unsigned short* frame_ant = (*frame_ant_ptr);
+
+ if( !frame_ant)
+ {
+ (*frame_ant_ptr) = frame_ant = malloc( w*h*sizeof(unsigned short) );
+ for( y = 0; y < h; y++ )
+ {
+ unsigned short* dst = &frame_ant[y*w];
+ unsigned char* src = frame_src + y*w;
+
+ for( x = 0; x < w; x++ )
+ {
+ dst[x] = src[x] << 8;
+ }
+ }
+ }
+
+ /* If no spatial coefficients, do temporal denoise only */
+ if( !horizontal[0] && !vertical[0] )
+ {
+ hqdn3d_denoise_temporal( frame_src,
+ frame_dst,
+ frame_ant,
+ w, h,
+ temporal);
+ return;
+ }
+
+ /* If no temporal coefficients, do spatial denoise only */
+ if( !temporal[0] )
+ {
+ hqdn3d_denoise_spatial( frame_src,
+ frame_dst,
+ line_ant,
+ w, h,
+ horizontal,
+ vertical);
+ return;
+ }
+
+ /* First pixel has no left nor top neighbor. Only previous frame */
+ line_ant[0] = pixel_ant = frame_src[0] << 16;
+
+ pixel_dst = hqdn3d_lowpass_mul( frame_ant[0]<<8,
+ pixel_ant,
+ temporal );
+
+ frame_ant[0] = ((pixel_dst+0x1000007F)>>8);
+ frame_dst[0] = ((pixel_dst+0x10007FFF)>>16);
+
+ /* First line has no top neighbor. Only left one for each pixel and last frame */
+ for( x = 1; x < w; x++ )
+ {
+ line_ant[x] = pixel_ant = hqdn3d_lowpass_mul( pixel_ant,
+ frame_src[x]<<16,
+ horizontal);
+
+ pixel_dst = hqdn3d_lowpass_mul( frame_ant[x]<<8,
+ pixel_ant,
+ temporal);
+
+ frame_ant[x] = ((pixel_dst+0x1000007F)>>8);
+ frame_dst[x] = ((pixel_dst+0x10007FFF)>>16);
+ }
+
+ /* The rest of the lines in the frame are normal */
+ for( y = 1; y < h; y++ )
+ {
+ unsigned int pixel_ant;
+ unsigned short * line_prev = &frame_ant[y*w];
+ line_offset_src += w, line_offset_dst += w;
+
+ /* First pixel on each line doesn't have previous pixel */
+ pixel_ant = frame_src[line_offset_src]<<16;
+ line_ant[0] = hqdn3d_lowpass_mul( line_ant[0],
+ pixel_ant,
+ vertical);
+ pixel_dst = hqdn3d_lowpass_mul( line_prev[0]<<8,
+ line_ant[0],
+ temporal);
+ line_prev[0] = ((pixel_dst+0x1000007F)>>8);
+
+ frame_dst[line_offset_dst] = ((pixel_dst+0x10007FFF)>>16);
+
+ /* The rest of the pixels in the line are normal */
+ for( x = 1; x < w; x++ )
+ {
+ unsigned int pixel_dst;
+ pixel_ant = hqdn3d_lowpass_mul( pixel_ant,
+ frame_src[line_offset_src+x]<<16,
+ horizontal );
+ line_ant[x] = hqdn3d_lowpass_mul( line_ant[x],
+ pixel_ant, vertical);
+ pixel_dst = hqdn3d_lowpass_mul( line_prev[x]<<8,
+ line_ant[x],
+ temporal );
+ line_prev[x] = ((pixel_dst+0x1000007F)>>8);
+
+ frame_dst[line_offset_dst+x] = ((pixel_dst+0x10007FFF)>>16);
+ }
+ }
+}
+
+hb_filter_private_t * hb_denoise_init( int pix_fmt,
+ int width,
+ int height,
+ char * settings )
+{
+ if( pix_fmt != PIX_FMT_YUV420P )
+ {
+ return 0;
+ }
+
+ hb_filter_private_t * pv = malloc( sizeof(struct hb_filter_private_s) );
+
+ pv->pix_fmt = pix_fmt;
+ pv->width[0] = width;
+ pv->height[0] = height;
+ pv->width[1] = pv->width[2] = width >> 1;
+ pv->height[1] = pv->height[2] = height >> 1;
+
+ double spatial_luma, temporal_luma, spatial_chroma, temporal_chroma;
+
+ if( settings )
+ {
+ switch( sscanf( settings, "%lf:%lf:%lf:%lf",
+ &spatial_luma, &spatial_chroma,
+ &temporal_luma, &temporal_chroma ) )
+ {
+ case 0:
+ spatial_luma = HQDN3D_SPATIAL_LUMA_DEFAULT;
+
+ spatial_chroma = HQDN3D_SPATIAL_CHROMA_DEFAULT;
+
+ temporal_luma = HQDN3D_TEMPORAL_LUMA_DEFAULT;
+
+ temporal_chroma = temporal_luma *
+ spatial_chroma / spatial_luma;
+ break;
+
+ case 1:
+ spatial_chroma = HQDN3D_SPATIAL_CHROMA_DEFAULT *
+ spatial_luma / HQDN3D_SPATIAL_LUMA_DEFAULT;
+
+ temporal_luma = HQDN3D_TEMPORAL_LUMA_DEFAULT *
+ spatial_luma / HQDN3D_SPATIAL_LUMA_DEFAULT;
+
+ temporal_chroma = temporal_luma *
+ spatial_chroma / spatial_luma;
+ break;
+
+ case 2:
+ temporal_luma = HQDN3D_TEMPORAL_LUMA_DEFAULT *
+ spatial_luma / HQDN3D_SPATIAL_LUMA_DEFAULT;
+
+ temporal_chroma = temporal_luma *
+ spatial_chroma / spatial_luma;
+ break;
+
+ case 3:
+ temporal_chroma = temporal_luma *
+ spatial_chroma / spatial_luma;
+ break;
+ }
+ }
+
+ pv->hqdn3d_line = malloc( width * sizeof(int) );
+
+ hqdn3d_precalc_coef( pv->hqdn3d_coef[0], spatial_luma );
+ hqdn3d_precalc_coef( pv->hqdn3d_coef[1], temporal_luma );
+ hqdn3d_precalc_coef( pv->hqdn3d_coef[2], spatial_chroma );
+ hqdn3d_precalc_coef( pv->hqdn3d_coef[3], temporal_chroma );
+
+ int buf_size = 3 * width * height / 2;
+ pv->buf_out = hb_buffer_init( buf_size );
+
+ return pv;
+}
+
+void hb_denoise_close( hb_filter_private_t * pv )
+{
+ if( !pv )
+ {
+ return;
+ }
+
+ if( pv->hqdn3d_line )
+ {
+ free( pv->hqdn3d_line );
+ pv->hqdn3d_line = NULL;
+ }
+ if( pv->hqdn3d_frame[0] )
+ {
+ free( pv->hqdn3d_frame[0] );
+ pv->hqdn3d_frame[0] = NULL;
+ }
+ if( pv->hqdn3d_frame[1] )
+ {
+ free( pv->hqdn3d_frame[1] );
+ pv->hqdn3d_frame[1] = NULL;
+ }
+ if( pv->hqdn3d_frame[2] )
+ {
+ free( pv->hqdn3d_frame[2] );
+ pv->hqdn3d_frame[2] = NULL;
+ }
+ if( pv->buf_out )
+ {
+ hb_buffer_close( &pv->buf_out );
+ }
+
+ free( pv );
+}
+
+int hb_denoise_work( const hb_buffer_t * buf_in,
+ hb_buffer_t ** buf_out,
+ int pix_fmt,
+ int width,
+ int height,
+ hb_filter_private_t * pv )
+{
+ if( !pv ||
+ pix_fmt != pv->pix_fmt ||
+ width != pv->width[0] ||
+ height != pv->height[0] )
+ {
+ return FILTER_FAILED;
+ }
+
+ avpicture_fill( &pv->pic_in, buf_in->data,
+ pix_fmt, width, height );
+
+ avpicture_fill( &pv->pic_out, pv->buf_out->data,
+ pix_fmt, width, height );
+
+ hqdn3d_denoise( pv->pic_in.data[0],
+ pv->pic_out.data[0],
+ pv->hqdn3d_line,
+ &pv->hqdn3d_frame[0],
+ pv->width[0],
+ pv->height[0],
+ pv->hqdn3d_coef[0],
+ pv->hqdn3d_coef[0],
+ pv->hqdn3d_coef[1] );
+
+ hqdn3d_denoise( pv->pic_in.data[1],
+ pv->pic_out.data[1],
+ pv->hqdn3d_line,
+ &pv->hqdn3d_frame[1],
+ pv->width[1],
+ pv->height[1],
+ pv->hqdn3d_coef[2],
+ pv->hqdn3d_coef[2],
+ pv->hqdn3d_coef[3] );
+
+ hqdn3d_denoise( pv->pic_in.data[2],
+ pv->pic_out.data[2],
+ pv->hqdn3d_line,
+ &pv->hqdn3d_frame[2],
+ pv->width[2],
+ pv->height[2],
+ pv->hqdn3d_coef[2],
+ pv->hqdn3d_coef[2],
+ pv->hqdn3d_coef[3] );
+
+ hb_buffer_copy_settings( pv->buf_out, buf_in );
+
+ *buf_out = pv->buf_out;
+
+ return FILTER_OK;
+}