Some decomb changes...
Gamma scaling
Dark_Shikari mentioned offhandedly the other month that:
Jason wrote:[17:20] [Dark_Shikari] make all filters gamma-aware
[17:20] [Dark_Shikari] most filters completely ignore gamma
[17:20] [Dark_Shikari] which can give surprisingly bad results sometimes
<snip>
[17:22] [Dark_Shikari] "gamma-aware" means that the filter understands that the difference between "30 and 31" luma is not the same as between "250 and 251"
http://handbrake.fr/irclogs/handbrake/h ... 5_pg3.html
...which was a real "oh, duh" moment for me.
Decomb *does* assume the difference between 250 and 251 is the same as the difference between 30 and 31. But the eye is more sensitive to some brightness than to others. The actual intensity of light as a percentage isn't 30/255 (11.76%), it's (30/255)^2.2 (0.9%). So the difference between 30 and 31 is:
Code: Select all
(30/255) ^ 2.2 - (31/255) ^ 2.2
==
0.0090214919 - 0.0096963287
==
-0.0006748368
Which is to say, there is hardly any perceptible difference.
Whereas 250-251 is:
Code: Select all
(250/255) ^ 2.2 - (251-255) ^ 2.2
==
0.957369576 - 0.965814654
==
-0.008445078
So a difference of 1 out of 255, which naively would always be a difference of 0.39%, can actually range from 0.07% to 0.84%
I modified decomb to scale values to the gamma when a new mode is applied, 128 (64 is reserved for bobbing). I quickly found that this lowered decomb's sensitivity. Because it is now looking for only changes in high-intensity parts of the image, it does not get as many hits. An extra from the Serenity DVD demonstrates this nicely.
To make what's going on a little clearer, I've added a new function that gets called in maskmode (32), which draws a box on the output. This box surrounds the area of the frame that made decomb decide the frame was combed. The box has dimensions equivalent to the block_width and block_height parameters of the filter, and the placement corresponds to the first block of pixels in the image where the number of combed pixels exceeds the block_threshold (except when a frame is blended, then it's the location of the *last* block of pixels in the image that exceeds *half* the block_threshold). If no pixels are combed, the box rests in the upper left corner of the frame.
Here is the raw frame:
Here are, in white, the pixels decomb currently thinks are combed (filtered with decomb options 295:2:6:9:1):
Here are, in white, the pixels decomb thinks are combed after scaling luminosity (filtered with decomb options 423:2:6:9:1):
As you see, the scaled one ignores combing in shadowed parts of the frame that are harder to see, while still catching the parts that are bright and easy to see.
Now, because it is being more discerning, I was able to turn up the sensitivity. I'm now using spatial and motion thresholds of 3 instead of 9 and 6 respectively, and a block threshold of 40 instead of 80.
With the gamma scaling and increased sensitivity, the output looks like this (filtered with decomb options 423:2:3:3:1:40):
Note that it looks like the unscaled output at current thresholds, but with less noise. However, lower motion and spatial thresholds do mean there is a chance for noise slipping through, potentially tripping the lower block threshold.
Filtering
So the second change I've made is to add filtering of the combing mask. When decomb analyzes a frame, it builds a black and white bitmap image the size of the picture frame, with white pixels to denote each combed pixel's location, and black to indicate uncombed.
If you've ever worked with Photoshop's selection tools, you probably know about a little trick to improve their efficacy. Say you want to select every pixel of a particular color. However, once you do so, you end up with a bunch of selections separated by small gaps where the colors are slightly different, as well as some small oddball selections you didn't want just because one pixel is the right color. The solution is to contract the selection by several pixels, then expand the selection by several pixels more, then contract it again. Contracting removes small selection areas, expanding recovers them if they are near bigger selection areas, and contracting again returns to the original selection shape...which is hopefully now more uniform.
I stole this erosion+dilation technique from EEDI2, which uses it to clean up its edge mask.
Decomb was, previously, doing something a little like this. It would filter out any combed pixels that did not have combed pixels to the immediate left or right of themselves. This was actually a bug -- I'd meant to filter out combed pixels that didn't have other combed pixels immediately above or below themselves -- but worked well enough. Now, in the initial erosion phase, it's filtering out any that don't have more than 2 pixels combed on any sides, which is a little more flexible.
Applying the new filter to the gamma-scaled combing mask produces this (filtered with decomb options 423:2:3:3:2:40):
Putting it all together
Decomb is now more sensitive to combing that people can easily see and less sensitive to combing that people can't easily see. This allows much lower thresholds for what constitutes combing, making it less likely to give false negatives on interlaced frames. Better filtering of the results means the filter should be more resilient to noise and less likely to give false positives on progressive frames.
In order to make these comparisons more clear, I've added a mode 512 to decomb to aid in debugging. It's to be used with mask mode (32) to composite the combing mask onto the output frame. The mask shows every pixel it thinks is combed in white, and every pixel it thinks is not combed in black. Compositing shows the combed pixels in white and the uncombed pixels as they would be output normally.
Fewer false negatives:
Example of an interlaced frame current decomb lets through and new decomb catches:
Here is what old decomb would do (--decomb=807:2:6:9:1):
The filter sees some pixels are combed, but doesn't see it as sufficiently combed to deinterlace it.
Here is what new decomb will do (--decomb=935:2:3:3:2:40):
It latches on to the combing on the side of Joss Whedon's nose and deinterlaces the frame.
On the hybrid clip that frame is from...
Old decomb stats are: deinterlaced 460 | blended 107 | unfiltered 752 | total 1319
New decomb stats are: deinterlaced 617 | blended 79 | unfiltered 623 | total 1319
Fewer false positives
One kind of progressive frame that can trip up decomb is vegetation. Put a bunch of tree branches in front of a camera, and all the criss-crossed lines can look like interlacing artifacts to the filter. For example, at the beginning of the TV series Lost, Jack Shephard opens his eyes on the jungle floor and looks up at a bamboo forest, swaying in the breeze.
The mask old decomb produced (--decomb=295:2:6:9:1):
New combing mask of the frame (--decomb=423:2:3:3:2:40):
Patch:
http://handbrake.fr/pastebin/pastebin.php?show=1414
Code: Select all
Index: libhb/decomb.c
===================================================================
--- libhb/decomb.c (revision 3295)
+++ libhb/decomb.c (working copy)
@@ -11,8 +11,8 @@
/*****
Parameters:
- Mode : Spatial metric : Motion thresh : Spatial thresh : Block thresh :
- Block width : Block height
+ Mode : Spatial metric : Motion thresh : Spatial thresh : Mask Filter Mode :
+ Block thresh : Block width : Block height
Appended for EEDI2:
Magnitude thresh : Variance thresh : Laplacian thresh : Dilation thresh :
@@ -22,7 +22,7 @@
Parity
Defaults:
- 7:2:6:9:80:16:16:10:20:20:4:2:50:24:1:-1
+ 7:2:6:9:1:80:16:16:10:20:20:4:2:50:24:1:-1
*****/
#define MODE_YADIF 1 // Use yadif
@@ -31,7 +31,13 @@
#define MODE_EEDI2 8 // Use EEDI2 interpolation
#define MODE_MCDEINT 16 // Post-process with mcdeint
#define MODE_MASK 32 // Output combing masks instead of pictures
+#define MODE_GAMMA 128 // Scale gamma when decombing
+#define MODE_FILTER 256 // Filter combing mask
+#define MODE_COMPOSITE 512 // Overlay combing mask onto picture
+#define FILTER_CLASSIC 1
+#define FILTER_ERODE_DILATE 2
+
/*****
These modes can be layered. For example, Yadif (1) + EEDI2 (8) = 9,
which will feed EEDI2 interpolations to yadif.
@@ -136,6 +142,7 @@
// Decomb parameters
int mode;
+ int filter_mode;
int spatial_metric;
int motion_threshold;
int spatial_threshold;
@@ -143,6 +150,8 @@
int block_width;
int block_height;
+ float gamma_lut[256];
+
// EEDI2 parameters
int magnitude_threshold;
int variance_threshold;
@@ -174,9 +183,15 @@
uint8_t * ref[4][3];
int ref_stride[3];
- /* Make a buffer to store a comb mask. */
+ /* Make buffers to store a comb masks. */
uint8_t * mask[3];
+ uint8_t * mask_filtered[3];
+ uint8_t * mask_temp[3];
+ int mask_box_x;
+ int mask_box_y;
+ uint8_t mask_box_color;
+
uint8_t * eedi_half[4][3];
uint8_t * eedi_full[5][3];
int * cx2;
@@ -311,6 +326,31 @@
}
}
+void draw_mask_box( hb_filter_private_t * pv )
+{
+ int x = pv->mask_box_x;
+ int y = pv->mask_box_y;
+ int box_width = pv->block_width;
+ int box_height = pv->block_height;
+ int stride = pv->ref_stride[0];
+ uint8_t * mskp = ( pv->mode & MODE_FILTER ) ? pv->mask_filtered[0] : pv->mask[0];
+
+
+ int block_x, block_y;
+ for( block_x = 0; block_x < box_width; block_x++)
+ {
+ mskp[y*stride+x+block_x] = 128;
+ mskp[(y+box_height)*stride+x+block_x] = 128;
+ }
+
+ for( block_y = 0; block_y < box_height; block_y++)
+ {
+ mskp[stride*(y+block_y)+x] = 128;
+ mskp[stride*(y+block_y) + x + box_width] = 128;
+ }
+
+}
+
void apply_mask_line( uint8_t * srcp,
uint8_t * mskp,
int width )
@@ -323,25 +363,34 @@
{
srcp[x] = 255;
}
+ if( mskp[x] == 128 )
+ {
+ srcp[x] = 128;
+ }
}
}
void apply_mask( hb_filter_private_t * pv )
{
+
+ /* draw_boxes */
+ draw_mask_box( pv );
+
int plane, height;
for( plane = 0; plane < 3; plane++ )
{
uint8_t * srcp = ( pv->mode & MODE_MCDEINT ) ? pv->pic_in.data[plane] : pv->pic_out.data[plane];
- uint8_t * mskp = pv->mask[plane];
+ uint8_t * mskp = ( pv->mode & MODE_FILTER ) ? pv->mask_filtered[plane] : pv->mask[plane];
for( height = 0; height < pv->height[plane]; height++ )
{
- if( pv->mode == MODE_MASK && plane == 0 )
+
+ if( !(pv->mode & MODE_COMPOSITE) && plane == 0 )
{
memcpy( srcp, mskp, pv->width[plane] );
}
- else if( pv->mode == MODE_MASK )
+ else if( !(pv->mode & MODE_COMPOSITE) )
{
memset( srcp, 128, pv->width[plane] );
}
@@ -349,11 +398,32 @@
{
apply_mask_line( srcp, mskp, pv->width[plane] );
}
+
+// if( ( pv->mode == MODE_MASK ||
+// ( pv->mode == ( MODE_MASK + MODE_GAMMA + MODE_FILTER) ) ||
+// ( pv->mode == ( MODE_MASK + MODE_FILTER ) ) ||
+// ( pv->mode == ( MODE_MASK + MODE_GAMMA ) ) )
+// && plane == 0 )
+// {
+// memcpy( srcp, mskp, pv->width[plane] );
+// }
+// else if( pv->mode == MODE_MASK ||
+// ( pv->mode == ( MODE_MASK + MODE_GAMMA + MODE_FILTER ) ) ||
+// ( pv->mode == ( MODE_MASK + MODE_FILTER ) ) ||
+// ( pv->mode == ( MODE_MASK + MODE_GAMMA ) ) )
+// {
+// memset( srcp, 128, pv->width[plane] );
+// }
+// else if( plane == 0 )
+// {
+// apply_mask_line( srcp, mskp, pv->width[plane] );
+// }
srcp += pv->pic_out.linesize[plane];
mskp += pv->ref_stride[plane];
}
}
+
}
static void store_ref( const uint8_t ** pic,
@@ -483,6 +553,307 @@
}
}
+void erode_combing_mask( hb_filter_private_t * pv )
+{
+ //Take in mask, output to mask_temp
+ int x, y, k;
+
+ uint8_t * cur;
+ uint8_t * dst;
+
+ int count;
+
+ int erosion_threshold = 2;
+
+ int ref;
+ for( k = 0; k < 1; k++ )
+ {
+ ref = pv->ref_stride[k];
+
+ for( y = 1; y < pv->height[k] -1; y++ )
+ {
+ cur = &pv->mask_temp[k][y*ref];
+ dst = &pv->mask_filtered[k][y*ref];
+
+ for( x = 1; x < pv->width[k]-1; x++ )
+ {
+ if( cur[0] == 0 )
+ {
+ dst[0] = 0;
+ cur++;
+ dst++;
+ continue;
+ }
+
+ count = 0;
+ if( cur[-ref-1] == 255 )
+ ++count;
+ if( cur[-ref] == 255 )
+ ++count;
+ if( cur[-ref+1] == 255 )
+ ++count;
+ if( cur[-1] == 255 )
+ ++count;
+ if( cur[+1] == 255 )
+ ++count;
+ if( cur[ref-1] == 255 )
+ ++count;
+ if( cur[ref] == 255 )
+ ++count;
+ if( cur[ref+1] == 255 )
+ ++count;
+
+ if( count < erosion_threshold )
+ dst[0] = 0;
+ else
+ dst[0] = 255;
+
+ cur++;
+ dst++;
+ }
+ }
+ }
+}
+
+void dilate_combing_mask( hb_filter_private_t * pv )
+{
+ //Take in mask_temp, output to mask
+ int x, y, k;
+
+ uint8_t * curp;
+ uint8_t * cur;
+ uint8_t * curn;
+ uint8_t * dst;
+
+ int count;
+
+ int dilation_threshold = 4;
+
+ int ref;
+ for( k = 0; k < 1; k++ )
+ {
+ ref = pv->ref_stride[k];
+
+ for( y = 1; y < pv->height[k] -1; y++ )
+ {
+ curp = &pv->mask_filtered[k][(y-1)*ref];
+ cur = &pv->mask_filtered[k][y*ref];
+ curn = &pv->mask_filtered[k][(y+1)*ref];
+ dst = &pv->mask_temp[k][y*ref];
+
+ for( x = 1; x < pv->width[k]-1; x++ )
+ {
+ if( cur[0] == 255 )
+ {
+ dst[0] = 255;
+ curp++;
+ cur++;
+ curn++;
+ dst++;
+ continue;
+ }
+
+ count = 0;
+ if( curp[-1] == 255 )
+ ++count;
+ if( curp[0] == 255 )
+ ++count;
+ if( curp[+1] == 255 )
+ ++count;
+ if( cur[-1] == 255 )
+ ++count;
+ if( cur[+1] == 255 )
+ ++count;
+ if( curn[-1] == 255 )
+ ++count;
+ if( curn[0] == 255 )
+ ++count;
+ if( curn[+1] == 255 )
+ ++count;
+
+ if( count >= dilation_threshold )
+ dst[0] = 255;
+ else
+ dst[0] = 0;
+
+ curp++;
+ cur++;
+ curn++;
+ dst++;
+ }
+ }
+ }
+}
+
+
+void filter_combing_mask( hb_filter_private_t * pv )
+{
+ int x, y, k;
+
+ uint8_t * curp;
+ uint8_t * cur;
+ uint8_t * curn;
+ uint8_t * dst;
+
+ int h_count, v_count;
+
+ int ref;
+ for( k = 0; k < 1; k++ )
+ {
+ ref = pv->ref_stride[k];
+
+ for( y = 0; y < pv->height[k] -1; y++ )
+ {
+ curp = &pv->mask[k][(y-1)*ref];
+ cur = &pv->mask[k][y*ref];
+ curn = &pv->mask[k][(y+1)*ref];
+ dst = (pv->filter_mode == FILTER_CLASSIC ) ? &pv->mask_filtered[k][y*ref] : &pv->mask_temp[k][y*ref] ;
+
+ for( x = 0; x < pv->width[k]-1; x++ )
+ {
+
+ h_count = v_count = 0;
+ if( x == 0 )
+ {
+ if( cur[0] == 255 && cur[1] == 255 )
+ h_count++;
+ }
+ else if( x == pv->width[k]-1 )
+ {
+ if( cur[-1] == 255 && cur[0] == 255 )
+ h_count++;
+ }
+ else
+ {
+ if(cur[-1] == 255 && cur[0] == 255 && cur[1] == 255 )
+ h_count++;
+ }
+
+ if( y == 0 )
+ {
+ if( cur[0] == 255 && curn[0] == 255 )
+ v_count++;
+ }
+ else if( y == pv->height[k]-1 )
+ {
+ if( curp[0] == 255 && cur[0] == 255 )
+ v_count++;
+ }
+ else
+ {
+ if( curp[0] == 255 && cur[0] == 255 && curn[0] == 255 )
+ v_count++;
+ }
+
+ if( h_count & pv->filter_mode == FILTER_CLASSIC )
+ dst[0] = 255;
+ else if( pv->filter_mode == FILTER_CLASSIC )
+ dst[0] = 0;
+ else if( h_count && v_count )
+ dst[0] = 255;
+ else
+ dst[0] = 0;
+
+ curp++;
+ cur++;
+ curn++;
+ dst++;
+ }
+ }
+ }
+}
+
+int check_filtered_combing_mask( hb_filter_private_t * pv )
+{
+ /* Go through the mask in X*Y blocks. If any of these windows
+ have threshold or more combed pixels, consider the whole
+ frame to be combed and send it on to be deinterlaced. */
+
+ /* Block mask threshold -- The number of pixels
+ in a block_width * block_height window of
+ he mask that need to show combing for the
+ whole frame to be seen as such. */
+ int threshold = pv->block_threshold;
+ int block_width = pv->block_width;
+ int block_height = pv->block_height;
+ int block_x, block_y;
+ int block_score = 0; int send_to_blend = 0;
+ uint8_t * mask_p;
+ int x, y, k;
+
+ for( k = 0; k < 1; k++ )
+ {
+ int ref_stride = pv->ref_stride[k];
+ pv->mask_box_x = -1;
+ pv->mask_box_y = -1;
+ pv->mask_box_color = 0;
+
+ for( y = 0; y < ( pv->height[k] - block_height ); y = y + block_height )
+ {
+ for( x = 0; x < ( pv->width[k] - block_width ); x = x + block_width )
+ {
+ block_score = 0;
+
+ for( block_y = 0; block_y < block_height; block_y++ )
+ {
+ int mask_y = y + block_y;
+ mask_p = &pv->mask_filtered[k][mask_y*ref_stride + x];
+
+ for( block_x = 0; block_x < block_width; block_x++ )
+ {
+
+ if( mask_p[ 0 ] == 255 )
+ block_score++;
+ mask_p++;
+ }
+ }
+
+ if( block_score >= ( threshold / 2 ) )
+ {
+#if 0
+ hb_log("decomb: frame %i | score %i | type %s", pv->deinterlaced_frames + pv->blended_frames + pv->unfiltered_frames + 1, block_score, pv->buf_settings->flags & 16 ? "Film" : "Video");
+#endif
+ pv->mask_box_x = x;
+ pv->mask_box_y = y;
+
+ if ( block_score <= threshold && !( pv->buf_settings->flags & 16) )
+ {
+ /* Blend video content that scores between
+ ( threshold / 2 ) and threshold. */
+ send_to_blend = 1;
+ pv->mask_box_color = 2;
+ }
+ else if( block_score > threshold )
+ {
+ if( pv->buf_settings->flags & 16 )
+ {
+ /* Blend progressive content above the threshold.*/
+ pv->mask_box_color = 2;
+ return 2;
+ }
+ else
+ {
+ /* Yadif deinterlace video content above the threshold. */
+ pv->mask_box_color = 1;
+ return 1;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if( send_to_blend )
+ {
+ return 2;
+ }
+ else
+ {
+ /* Consider this frame to be uncombed. */
+ return 0;
+ }
+}
+
int check_combing_mask( hb_filter_private_t * pv )
{
/* Go through the mask in X*Y blocks. If any of these windows
@@ -539,6 +910,27 @@
mask_p[ 1 ] == 255 )
block_score++;
}
+
+// if( (y + block_y) == 0 )
+// {
+// if( mask_p[ 0 ] == 255 &&
+// mask_p[ ref_stride ] == 255 )
+// block_score++;
+// }
+// else if( (y + block_y) == (pv->height[k] -1) )
+// {
+// if( mask_p[ -ref_stride ] == 255 &&
+// mask_p[ 0 ] == 255 )
+// block_score++;
+// }
+// else
+// {
+// if( mask_p[ -ref_stride ] == 255 &&
+// mask_p[ 0 ] == 255 &&
+// mask_p[ +ref_stride ] == 255 )
+// block_score++;
+// }
+//
mask_p++;
}
}
@@ -548,22 +940,29 @@
#if 0
hb_log("decomb: frame %i | score %i | type %s", pv->deinterlaced_frames + pv->blended_frames + pv->unfiltered_frames + 1, block_score, pv->buf_settings->flags & 16 ? "Film" : "Video");
#endif
+
+ pv->mask_box_x = x;
+ pv->mask_box_y = y;
+
if ( block_score <= threshold && !( pv->buf_settings->flags & 16) )
{
/* Blend video content that scores between
( threshold / 2 ) and threshold. */
send_to_blend = 1;
+ pv->mask_box_color = 2;
}
else if( block_score > threshold )
{
if( pv->buf_settings->flags & 16 )
{
/* Blend progressive content above the threshold.*/
+ pv->mask_box_color = 2;
return 2;
}
else
{
/* Yadif deinterlace video content above the threshold. */
+ pv->mask_box_color = 1;
return 1;
}
}
@@ -583,6 +982,144 @@
}
}
+void build_gamma_lut( hb_filter_private_t * pv )
+{
+ int i;
+ for( i = 0; i < 256; i++ )
+ {
+ pv->gamma_lut[i] = pow( ( (float)i / (float)255 ), 2.2f );
+ }
+}
+
+float scale_gamma( int pixel, hb_filter_private_t * pv )
+{
+ return pv->gamma_lut[pixel];
+}
+
+void detect_gamma_combed_segment( hb_filter_private_t * pv, int segment_start, int segment_stop )
+{
+ /* A mish-mash of various comb detection tricks
+ picked up from neuron2's Decomb plugin for
+ AviSynth and tritical's IsCombedT and
+ IsCombedTIVTC plugins. */
+
+ int x, y, k, width, height;
+
+ /* Comb scoring algorithm */
+ float spatial_metric = (float)pv->spatial_metric / (float)255;
+ /* Motion threshold */
+ float mthresh = (float)pv->motion_threshold / (float)255;
+ /* Spatial threshold */
+ float athresh = (float)pv->spatial_threshold / (float)255;
+ float athresh_squared = athresh * athresh;
+ float athresh6 = 6 *athresh;
+
+ /* One pas for Y, one pass for U, one pass for V */
+ for( k = 0; k < 1; k++ )
+ {
+ int ref_stride = pv->ref_stride[k];
+ width = pv->width[k];
+ height = pv->height[k];
+
+ /* Comb detection has to start at y = 2 and end at
+ y = height - 2, because it needs to examine
+ 2 pixels above and 2 below the current pixel. */
+ if( segment_start < 2 )
+ segment_start = 2;
+ if( segment_stop > height - 2 )
+ segment_stop = height - 2;
+
+ for( y = segment_start; y < segment_stop; y++ )
+ {
+ /* These are just to make the buffer locations easier to read. */
+ int up_2 = -2*ref_stride ;
+ int up_1 = -1*ref_stride;
+ int down_1 = ref_stride;
+ int down_2 = 2*ref_stride;
+
+ /* We need to examine a column of 5 pixels
+ in the prev, cur, and next frames. */
+ uint8_t * cur = &pv->ref[1][k][y*ref_stride];
+ uint8_t * prev = &pv->ref[0][k][y*ref_stride];
+ uint8_t * next = &pv->ref[2][k][y*ref_stride];
+ uint8_t * mask = &pv->mask[k][y*ref_stride];
+
+ for( x = 0; x < width; x++ )
+ {
+ float up_diff = pv->gamma_lut[cur[0]] - pv->gamma_lut[cur[up_1]];
+ float down_diff = pv->gamma_lut[cur[0]] - pv->gamma_lut[cur[down_1]];
+
+ if( ( up_diff > athresh && down_diff > athresh ) ||
+ ( up_diff < -athresh && down_diff < -athresh ) )
+ {
+ /* The pixel above and below are different,
+ and they change in the same "direction" too.*/
+ int motion = 0;
+ if( mthresh > 0 )
+ {
+ /* Make sure there's sufficient motion between frame t-1 to frame t+1. */
+ if( fabs( pv->gamma_lut[prev[0]] - pv->gamma_lut[cur[0]] ) > mthresh &&
+ fabs( pv->gamma_lut[cur[up_1]] - pv->gamma_lut[next[up_1]] ) > mthresh &&
+ fabs( pv->gamma_lut[cur[down_1]] - pv->gamma_lut[next[down_1]] ) > mthresh )
+ motion++;
+ if( fabs( pv->gamma_lut[next[0]] - pv->gamma_lut[cur[0]] ) > mthresh &&
+ fabs( pv->gamma_lut[prev[up_1]] - pv->gamma_lut[cur[up_1]] ) > mthresh &&
+ fabs( pv->gamma_lut[prev[down_1]] - pv->gamma_lut[cur[down_1]] ) > mthresh )
+ motion++;
+
+// hb_log("prev->cur motion: %f, mthresh: %f", fabs( scale_gamma( prev[0] ) - scale_gamma( cur[0] ) ), mthresh);
+ }
+ else
+ {
+ /* User doesn't want to check for motion,
+ so move on to the spatial check. */
+ motion = 1;
+ }
+
+ if( motion || ( pv->deinterlaced_frames==0 && pv->blended_frames==0 && pv->unfiltered_frames==0) )
+ {
+
+ /* Tritical's noise-resistant combing scorer.
+ The check is done on a bob+blur convolution. */
+ float combing = fabs( pv->gamma_lut[cur[up_2]]
+ + ( 4 * pv->gamma_lut[cur[0]] )
+ + pv->gamma_lut[cur[down_2]]
+ - ( 3 * ( pv->gamma_lut[cur[up_1]]
+ + pv->gamma_lut[cur[down_1]] ) ) );
+ /* If the frame is sufficiently combed,
+ then mark it down on the mask as 255. */
+
+ // hb_log("combing: %f, athresh6: %f", combing, athresh6);
+ if( combing > athresh6 )
+ {
+ mask[0] = 255;
+ }
+ else
+ {
+ mask[0] = 0;
+ }
+
+ }
+ else
+ {
+ mask[0] = 0;
+ }
+ }
+ else
+ {
+ mask[0] = 0;
+ }
+
+ cur++;
+ prev++;
+ next++;
+ mask++;
+ }
+ }
+ }
+}
+
+
void detect_combed_segment( hb_filter_private_t * pv, int segment_start, int segment_stop )
{
/* A mish-mash of various comb detection tricks
@@ -950,7 +1487,14 @@
segment_stop = ( h / pv->cpu_count ) * ( segment + 1 );
}
- detect_combed_segment( pv, segment_start, segment_stop );
+ if( pv->mode & MODE_GAMMA )
+ {
+ detect_gamma_combed_segment( pv, segment_start, segment_stop );
+ }
+ else
+ {
+ detect_combed_segment( pv, segment_start, segment_stop );
+ }
}
/*
* Finished this segment, let everyone know.
@@ -988,7 +1532,21 @@
hb_unlock( pv->decomb_complete_lock[segment] );
}
- return check_combing_mask( pv );
+ if( pv->mode & MODE_FILTER )
+ {
+ filter_combing_mask( pv );
+ if( pv->filter_mode == FILTER_ERODE_DILATE )
+ {
+ erode_combing_mask( pv );
+ dilate_combing_mask( pv );
+ erode_combing_mask( pv );
+ }
+ return check_filtered_combing_mask( pv );
+ }
+ else
+ {
+ return check_combing_mask( pv );
+ }
}
static void yadif_filter_line( uint8_t *dst,
@@ -1458,7 +2016,7 @@
if( pv->mode & MODE_MASK && pv->spatial_metric >= 0 )
{
- if( pv->mode == MODE_MASK || is_combed )
+ if( pv->mode == MODE_MASK || (pv->mode & MODE_MASK && pv->mode & MODE_GAMMA) || is_combed || (pv->mode & MODE_MASK && pv->mode & MODE_FILTER ))
apply_mask( pv );
}
}
@@ -1606,6 +2164,8 @@
pv->width[1] = pv->width[2] = width >> 1;
pv->height[1] = pv->height[2] = height >> 1;
+ build_gamma_lut( pv );
+
pv->buf_out[0] = hb_video_buffer_init( width, height );
pv->buf_out[1] = hb_video_buffer_init( width, height );
pv->buf_settings = hb_buffer_init( 0 );
@@ -1617,6 +2177,7 @@
pv->yadif_ready = 0;
pv->mode = MODE_YADIF | MODE_BLEND | MODE_CUBIC;
+ pv->filter_mode = FILTER_ERODE_DILATE;
pv->spatial_metric = 2;
pv->motion_threshold = 6;
pv->spatial_threshold = 9;
@@ -1640,11 +2201,12 @@
if( settings )
{
- sscanf( settings, "%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d",
+ sscanf( settings, "%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d",
&pv->mode,
&pv->spatial_metric,
&pv->motion_threshold,
&pv->spatial_threshold,
+ &pv->filter_mode,
&pv->block_threshold,
&pv->block_width,
&pv->block_height,
@@ -1683,7 +2245,7 @@
}
}
- /* Allocate a buffer to store a comb mask. */
+ /* Allocate buffers to store comb masks. */
for( i = 0; i < 3; i++ )
{
int is_chroma = !!i;
@@ -1691,6 +2253,8 @@
int h = ((pv->height[0]+6+ 31) & (~31))>>is_chroma;
pv->mask[i] = calloc( 1, w*h*sizeof(uint8_t) ) + 3*w;
+ pv->mask_filtered[i] = calloc( 1, w*h*sizeof(uint8_t) ) + 3*w;
+ pv->mask_temp[i] = calloc( 1, w*h*sizeof(uint8_t) ) + 3*w;
}
if( pv->mode & MODE_EEDI2 )
@@ -1950,15 +2514,28 @@
}
}
- /* Cleanup combing mask. */
+ /* Cleanup combing masks. */
for( i = 0; i<3*3; i++ )
{
uint8_t **p = &pv->mask[i/3];
+ uint8_t **p2 = &pv->mask_filtered[i/3];
+ uint8_t **p3 = &pv->mask_temp[i/3];
+
if (*p)
{
free( *p - 3*pv->ref_stride[i/3] );
*p = NULL;
}
+ if (*p2)
+ {
+ free( *p2 - 3*pv->ref_stride[i/3] );
+ *p2 = NULL;
+ }
+ if (*p3)
+ {
+ free( *p3 - 3*pv->ref_stride[i/3] );
+ *p3 = NULL;
+ }
}
if( pv->mode & MODE_EEDI2 )