2022-05-24 11:20:14 +02:00
/*
* Copyright ( c ) 2021 Thilo Borgmann < thilo . borgmann _at_ mail . de >
*
* This file is part of FFmpeg .
*
* FFmpeg is free software ; you can redistribute it and / or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation ; either
* version 2.1 of the License , or ( at your option ) any later version .
*
* FFmpeg 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
* Lesser General Public License for more details .
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg ; if not , write to the Free Software
* Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 USA
*/
/**
* @ file
* No - reference blockdetect filter
*
* Implementing :
* Remco Muijs and Ihor Kirenko : " A no-reference blocking artifact measure for adaptive video processing. " 2005 13 th European signal processing conference . IEEE , 2005.
* http : //www.eurasip.org/Proceedings/Eusipco/Eusipco2005/defevent/papers/cr1042.pdf
*
* @ author Thilo Borgmann < thilo . borgmann _at_ mail . de >
*/
# include "libavutil/imgutils.h"
# include "libavutil/opt.h"
# include "internal.h"
2023-08-03 14:37:51 +02:00
# include "video.h"
2022-05-24 11:20:14 +02:00
typedef struct BLKContext {
const AVClass * class ;
int hsub , vsub ;
int nb_planes ;
int period_min ; // minimum period to search for
int period_max ; // maximum period to search for
int planes ; // number of planes to filter
double block_total ;
uint64_t nb_frames ;
float * gradients ;
} BLKContext ;
# define OFFSET(x) offsetof(BLKContext, x)
# define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
static const AVOption blockdetect_options [ ] = {
{ " period_min " , " Minimum period to search for " , OFFSET ( period_min ) , AV_OPT_TYPE_INT , { . i64 = 3 } , 2 , 32 , FLAGS } ,
{ " period_max " , " Maximum period to search for " , OFFSET ( period_max ) , AV_OPT_TYPE_INT , { . i64 = 24 } , 2 , 64 , FLAGS } ,
{ " planes " , " set planes to filter " , OFFSET ( planes ) , AV_OPT_TYPE_INT , { . i64 = 1 } , 0 , 15 , FLAGS } ,
{ NULL }
} ;
AVFILTER_DEFINE_CLASS ( blockdetect ) ;
static int blockdetect_config_input ( AVFilterLink * inlink )
{
AVFilterContext * ctx = inlink - > dst ;
BLKContext * s = ctx - > priv ;
const int bufsize = inlink - > w * inlink - > h ;
const AVPixFmtDescriptor * pix_desc ;
pix_desc = av_pix_fmt_desc_get ( inlink - > format ) ;
s - > hsub = pix_desc - > log2_chroma_w ;
s - > vsub = pix_desc - > log2_chroma_h ;
s - > nb_planes = av_pix_fmt_count_planes ( inlink - > format ) ;
s - > gradients = av_calloc ( bufsize , sizeof ( * s - > gradients ) ) ;
if ( ! s - > gradients )
return AVERROR ( ENOMEM ) ;
return 0 ;
}
static float calculate_blockiness ( BLKContext * s , int w , int h ,
float * grad , int grad_linesize ,
uint8_t * src , int src_linesize )
{
float block = 0.0f ;
float nonblock = 0.0f ;
int block_count = 0 ;
int nonblock_count = 0 ;
float ret = 0 ;
// Calculate BS in horizontal and vertical directions according to (1)(2)(3).
// Also try to find integer pixel periods (grids) even for scaled images.
// In case of fractional periods, FFMAX of current and neighbor pixels
// can help improve the correlation with MQS.
// Skip linear correction term (4)(5), as it appears only valid for their own test samples.
// horizontal blockiness (fixed width)
for ( int j = 1 ; j < h ; j + + ) {
for ( int i = 3 ; i < w - 4 ; i + + ) {
float temp = 0.0f ;
grad [ j * grad_linesize + i ] =
abs ( src [ j * src_linesize + i + 0 ] - src [ j * src_linesize + i + 1 ] ) ;
temp + = abs ( src [ j * src_linesize + i + 1 ] - src [ j * src_linesize + i + 2 ] ) ;
temp + = abs ( src [ j * src_linesize + i + 2 ] - src [ j * src_linesize + i + 3 ] ) ;
temp + = abs ( src [ j * src_linesize + i + 3 ] - src [ j * src_linesize + i + 4 ] ) ;
temp + = abs ( src [ j * src_linesize + i - 0 ] - src [ j * src_linesize + i - 1 ] ) ;
temp + = abs ( src [ j * src_linesize + i - 1 ] - src [ j * src_linesize + i - 2 ] ) ;
temp + = abs ( src [ j * src_linesize + i - 2 ] - src [ j * src_linesize + i - 3 ] ) ;
temp = FFMAX ( 1 , temp ) ;
grad [ j * grad_linesize + i ] / = temp ;
// use first row to store acculated results
grad [ i ] + = grad [ j * grad_linesize + i ] ;
}
}
// find horizontal period
for ( int period = s - > period_min ; period < s - > period_max + 1 ; period + + ) {
float temp ;
block = 0 ;
nonblock = 0 ;
block_count = 0 ;
nonblock_count = 0 ;
for ( int i = 3 ; i < w - 4 ; i + + ) {
if ( ( i % period ) = = ( period - 1 ) ) {
block + = FFMAX ( FFMAX ( grad [ i + 0 ] , grad [ i + 1 ] ) , grad [ i - 1 ] ) ;
block_count + + ;
} else {
nonblock + = grad [ i ] ;
nonblock_count + + ;
}
}
2022-06-04 13:45:30 +02:00
if ( block_count & & nonblock_count ) {
2022-06-04 13:47:31 +02:00
temp = ( block / block_count ) / ( nonblock / nonblock_count ) ;
ret = FFMAX ( ret , temp ) ;
2022-06-04 13:45:30 +02:00
}
2022-05-24 11:20:14 +02:00
}
// vertical blockiness (fixed height)
block_count = 0 ;
for ( int j = 3 ; j < h - 4 ; j + + ) {
for ( int i = 1 ; i < w ; i + + ) {
float temp = 0.0f ;
grad [ j * grad_linesize + i ] =
abs ( src [ ( j + 0 ) * src_linesize + i ] - src [ ( j + 1 ) * src_linesize + i ] ) ;
temp + = abs ( src [ ( j + 1 ) * src_linesize + i ] - src [ ( j + 2 ) * src_linesize + i ] ) ;
temp + = abs ( src [ ( j + 2 ) * src_linesize + i ] - src [ ( j + 3 ) * src_linesize + i ] ) ;
temp + = abs ( src [ ( j + 3 ) * src_linesize + i ] - src [ ( j + 4 ) * src_linesize + i ] ) ;
temp + = abs ( src [ ( j - 0 ) * src_linesize + i ] - src [ ( j - 1 ) * src_linesize + i ] ) ;
temp + = abs ( src [ ( j - 1 ) * src_linesize + i ] - src [ ( j - 2 ) * src_linesize + i ] ) ;
temp + = abs ( src [ ( j - 2 ) * src_linesize + i ] - src [ ( j - 3 ) * src_linesize + i ] ) ;
temp = FFMAX ( 1 , temp ) ;
grad [ j * grad_linesize + i ] / = temp ;
// use first column to store accumulated results
grad [ j * grad_linesize ] + = grad [ j * grad_linesize + i ] ;
}
}
// find vertical period
for ( int period = s - > period_min ; period < s - > period_max + 1 ; period + + ) {
float temp ;
block = 0 ;
nonblock = 0 ;
block_count = 0 ;
nonblock_count = 0 ;
for ( int j = 3 ; j < h - 4 ; j + + ) {
if ( ( j % period ) = = ( period - 1 ) ) {
block + = FFMAX ( FFMAX ( grad [ ( j + 0 ) * grad_linesize ] ,
grad [ ( j + 1 ) * grad_linesize ] ) ,
grad [ ( j - 1 ) * grad_linesize ] ) ;
block_count + + ;
} else {
nonblock + = grad [ j * grad_linesize ] ;
nonblock_count + + ;
}
}
2022-06-04 13:45:30 +02:00
if ( block_count & & nonblock_count ) {
2022-06-04 13:47:31 +02:00
temp = ( block / block_count ) / ( nonblock / nonblock_count ) ;
ret = FFMAX ( ret , temp ) ;
2022-06-04 13:45:30 +02:00
}
2022-05-24 11:20:14 +02:00
}
// return highest value of horz||vert
return ret ;
}
static void set_meta ( AVDictionary * * metadata , const char * key , float d )
{
char value [ 128 ] ;
snprintf ( value , sizeof ( value ) , " %f " , d ) ;
av_dict_set ( metadata , key , value , 0 ) ;
}
static int blockdetect_filter_frame ( AVFilterLink * inlink , AVFrame * in )
{
AVFilterContext * ctx = inlink - > dst ;
BLKContext * s = ctx - > priv ;
AVFilterLink * outlink = ctx - > outputs [ 0 ] ;
const int inw = inlink - > w ;
const int inh = inlink - > h ;
float * gradients = s - > gradients ;
float block = 0.0f ;
int nplanes = 0 ;
AVDictionary * * metadata ;
metadata = & in - > metadata ;
for ( int plane = 0 ; plane < s - > nb_planes ; plane + + ) {
int hsub = plane = = 1 | | plane = = 2 ? s - > hsub : 0 ;
int vsub = plane = = 1 | | plane = = 2 ? s - > vsub : 0 ;
int w = AV_CEIL_RSHIFT ( inw , hsub ) ;
int h = AV_CEIL_RSHIFT ( inh , vsub ) ;
if ( ! ( ( 1 < < plane ) & s - > planes ) )
continue ;
nplanes + + ;
block + = calculate_blockiness ( s , w , h , gradients , w , in - > data [ plane ] , in - > linesize [ plane ] ) ;
}
if ( nplanes )
block / = nplanes ;
s - > block_total + = block ;
// write stats
av_log ( ctx , AV_LOG_VERBOSE , " block: %.7f \n " , block ) ;
set_meta ( metadata , " lavfi.block " , block ) ;
s - > nb_frames = inlink - > frame_count_in ;
return ff_filter_frame ( outlink , in ) ;
}
static av_cold void blockdetect_uninit ( AVFilterContext * ctx )
{
BLKContext * s = ctx - > priv ;
if ( s - > nb_frames > 0 ) {
av_log ( ctx , AV_LOG_INFO , " block mean: %.7f \n " ,
s - > block_total / s - > nb_frames ) ;
}
av_freep ( & s - > gradients ) ;
}
static const enum AVPixelFormat pix_fmts [ ] = {
AV_PIX_FMT_GRAY8 ,
AV_PIX_FMT_GBRP , AV_PIX_FMT_GBRAP ,
AV_PIX_FMT_YUV422P , AV_PIX_FMT_YUV420P ,
AV_PIX_FMT_YUV444P , AV_PIX_FMT_YUV440P ,
AV_PIX_FMT_YUV411P , AV_PIX_FMT_YUV410P ,
AV_PIX_FMT_YUVJ440P , AV_PIX_FMT_YUVJ411P , AV_PIX_FMT_YUVJ420P ,
AV_PIX_FMT_YUVJ422P , AV_PIX_FMT_YUVJ444P ,
AV_PIX_FMT_YUVA444P , AV_PIX_FMT_YUVA422P , AV_PIX_FMT_YUVA420P ,
AV_PIX_FMT_NONE
} ;
static const AVFilterPad blockdetect_inputs [ ] = {
{
. name = " default " ,
. type = AVMEDIA_TYPE_VIDEO ,
. config_props = blockdetect_config_input ,
. filter_frame = blockdetect_filter_frame ,
} ,
} ;
const AVFilter ff_vf_blockdetect = {
. name = " blockdetect " ,
. description = NULL_IF_CONFIG_SMALL ( " Blockdetect filter. " ) ,
. priv_size = sizeof ( BLKContext ) ,
. uninit = blockdetect_uninit ,
FILTER_PIXFMTS_ARRAY ( pix_fmts ) ,
FILTER_INPUTS ( blockdetect_inputs ) ,
2023-08-03 14:37:51 +02:00
FILTER_OUTPUTS ( ff_video_default_filterpad ) ,
2022-05-24 11:20:14 +02:00
. priv_class = & blockdetect_class ,
. flags = AVFILTER_FLAG_METADATA_ONLY ,
} ;