You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

350 lines
17KB

  1. /*
  2. * Copyright (C) 2019 Leo Zhang <leozhang@qiyi.com>
  3. * This file is part of FFmpeg.
  4. *
  5. * FFmpeg is free software; you can redistribute it and/or
  6. * modify it under the terms of the GNU Lesser General Public
  7. * License as published by the Free Software Foundation; either
  8. * version 2.1 of the License, or (at your option) any later version.
  9. *
  10. * FFmpeg is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  13. * Lesser General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Lesser General Public
  16. * License along with FFmpeg; if not, write to the Free Software
  17. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  18. */
  19. /**
  20. * @file
  21. * yaep(yet another edge preserving) blur filter
  22. *
  23. * This implementation is based on an algorithm described in
  24. * "J. S. Lee, Digital image enhancement and noise filtering by use of local statistics, IEEE Trans. Pattern
  25. * Anal. Mach. Intell. PAMI-2, 1980."
  26. */
  27. #include "libavutil/opt.h"
  28. #include "libavutil/imgutils.h"
  29. #include "avfilter.h"
  30. #include "internal.h"
  31. typedef struct YAEPContext {
  32. const AVClass *class;
  33. int planes;
  34. int radius;
  35. int sigma;
  36. int nb_planes;
  37. int planewidth[4];
  38. int planeheight[4];
  39. int depth;
  40. uint64_t *sat; ///< summed area table
  41. uint64_t *square_sat; ///< square summed area table
  42. int sat_linesize;
  43. int (*pre_calculate_row)(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs);
  44. int (*filter_slice )(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs);
  45. } YAEPContext;
  46. static av_cold void uninit(AVFilterContext *ctx)
  47. {
  48. YAEPContext *s = ctx->priv;
  49. av_freep(&s->sat);
  50. av_freep(&s->square_sat);
  51. }
  52. static int query_formats(AVFilterContext *ctx)
  53. {
  54. static const enum AVPixelFormat pix_fmts[] = {
  55. AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV440P,
  56. AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P,
  57. AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUVA420P, AV_PIX_FMT_YUV420P,
  58. AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ420P,
  59. AV_PIX_FMT_YUVJ411P, AV_PIX_FMT_YUV411P, AV_PIX_FMT_YUV410P,
  60. AV_PIX_FMT_YUV420P9, AV_PIX_FMT_YUV422P9, AV_PIX_FMT_YUV444P9,
  61. AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV444P10,
  62. AV_PIX_FMT_YUV420P12, AV_PIX_FMT_YUV422P12, AV_PIX_FMT_YUV444P12, AV_PIX_FMT_YUV440P12,
  63. AV_PIX_FMT_YUV420P14, AV_PIX_FMT_YUV422P14, AV_PIX_FMT_YUV444P14,
  64. AV_PIX_FMT_YUV420P16, AV_PIX_FMT_YUV422P16, AV_PIX_FMT_YUV444P16,
  65. AV_PIX_FMT_YUVA420P9, AV_PIX_FMT_YUVA422P9, AV_PIX_FMT_YUVA444P9,
  66. AV_PIX_FMT_YUVA420P10, AV_PIX_FMT_YUVA422P10, AV_PIX_FMT_YUVA444P10,
  67. AV_PIX_FMT_YUVA422P12, AV_PIX_FMT_YUVA444P12,
  68. AV_PIX_FMT_YUVA420P16, AV_PIX_FMT_YUVA422P16, AV_PIX_FMT_YUVA444P16,
  69. AV_PIX_FMT_GBRP, AV_PIX_FMT_GBRP9, AV_PIX_FMT_GBRP10,
  70. AV_PIX_FMT_GBRP12, AV_PIX_FMT_GBRP14, AV_PIX_FMT_GBRP16,
  71. AV_PIX_FMT_GBRAP, AV_PIX_FMT_GBRAP10, AV_PIX_FMT_GBRAP12, AV_PIX_FMT_GBRAP16,
  72. AV_PIX_FMT_GRAY8, AV_PIX_FMT_GRAY9, AV_PIX_FMT_GRAY10, AV_PIX_FMT_GRAY12, AV_PIX_FMT_GRAY14, AV_PIX_FMT_GRAY16,
  73. AV_PIX_FMT_NONE
  74. };
  75. return ff_set_common_formats(ctx, ff_make_format_list(pix_fmts));
  76. }
  77. typedef struct ThreadData {
  78. int width;
  79. int height;
  80. int src_linesize;
  81. int dst_linesize;
  82. uint8_t *src;
  83. uint8_t *dst;
  84. } ThreadData;
  85. #define PRE_CALCULATE_ROW(type, name) \
  86. static int pre_calculate_row_##name(AVFilterContext *ctx, void *arg, \
  87. int jobnr, int nb_jobs) \
  88. { \
  89. ThreadData *td = arg; \
  90. YAEPContext *s = ctx->priv; \
  91. \
  92. const int width = td->width; \
  93. const int height = td->height; \
  94. const int linesize = td->src_linesize / sizeof(type); \
  95. const int sat_linesize = s->sat_linesize; \
  96. \
  97. const int starty = height * jobnr / nb_jobs; \
  98. const int endy = height * (jobnr+1) / nb_jobs; \
  99. \
  100. uint64_t *sat = s->sat + (starty + 1) * sat_linesize; \
  101. uint64_t *square_sat = s->square_sat + (starty + 1) * sat_linesize; \
  102. const type *src = (const type *)td->src + starty * linesize; \
  103. \
  104. int x, y; \
  105. \
  106. for (y = starty; y < endy; y++) { \
  107. for (x = 0; x < width; x++) { \
  108. sat[x+1] = sat[x] + src[x]; \
  109. square_sat[x+1] = square_sat[x] + (uint64_t)src[x] * src[x]; \
  110. } \
  111. sat += sat_linesize; \
  112. square_sat += sat_linesize; \
  113. src += linesize; \
  114. } \
  115. \
  116. return 0; \
  117. }
  118. PRE_CALCULATE_ROW(uint8_t, byte)
  119. PRE_CALCULATE_ROW(uint16_t, word)
  120. static int pre_calculate_col(AVFilterContext *ctx, void *arg,
  121. int jobnr, int nb_jobs)
  122. {
  123. ThreadData *td = arg;
  124. YAEPContext *s = ctx->priv;
  125. const int width = td->width;
  126. const int height = td->height;
  127. const int sat_linesize = s->sat_linesize;
  128. const int startx = width * jobnr / nb_jobs;
  129. const int endx = width * (jobnr + 1) / nb_jobs;
  130. uint64_t *sat, *square_sat;
  131. int x, y;
  132. for (x = startx; x < endx; x++) {
  133. sat = s->sat + x + 1;
  134. square_sat = s->square_sat + x + 1;
  135. for (y = 0; y < height; y++) {
  136. *(sat+sat_linesize) += *sat;
  137. *(square_sat+sat_linesize) += *square_sat;
  138. sat += sat_linesize;
  139. square_sat += sat_linesize;
  140. }
  141. }
  142. return 0;
  143. }
  144. #define FILTER_SLICE(type, name) \
  145. static int filter_slice_##name(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) \
  146. { \
  147. ThreadData *td = arg; \
  148. YAEPContext *s = ctx->priv; \
  149. \
  150. const int width = td->width; \
  151. const int height = td->height; \
  152. const int src_linesize = td->src_linesize / sizeof(type); \
  153. const int dst_linesize = td->dst_linesize / sizeof(type); \
  154. const int sat_linesize = s->sat_linesize; \
  155. const int sigma = s->sigma; \
  156. const int radius = s->radius; \
  157. \
  158. uint64_t *sat = s->sat; \
  159. uint64_t *square_sat = s->square_sat; \
  160. const type *src = (const type *)td->src; \
  161. type *dst = (type *)td->dst; \
  162. \
  163. const int starty = height * jobnr / nb_jobs; \
  164. const int endy = height * (jobnr + 1) / nb_jobs; \
  165. \
  166. int x, y; \
  167. int lower_x, higher_x; \
  168. int lower_y, higher_y; \
  169. int dist_y, count; \
  170. uint64_t sum, square_sum, mean, var; \
  171. \
  172. for (y = starty; y < endy; y++) { \
  173. lower_y = y - radius < 0 ? 0 : y - radius; \
  174. higher_y = y + radius + 1 > height ? height : y + radius + 1; \
  175. dist_y = higher_y - lower_y; \
  176. for (x = 0; x < width; x++) { \
  177. lower_x = x - radius < 0 ? 0 : x - radius; \
  178. higher_x = x + radius + 1 > width ? width : x + radius + 1; \
  179. count = dist_y * (higher_x - lower_x); \
  180. sum = sat[higher_y * sat_linesize + higher_x] \
  181. - sat[higher_y * sat_linesize + lower_x] \
  182. - sat[lower_y * sat_linesize + higher_x] \
  183. + sat[lower_y * sat_linesize + lower_x]; \
  184. square_sum = square_sat[higher_y * sat_linesize + higher_x] \
  185. - square_sat[higher_y * sat_linesize + lower_x] \
  186. - square_sat[lower_y * sat_linesize + higher_x] \
  187. + square_sat[lower_y * sat_linesize + lower_x]; \
  188. mean = sum / count; \
  189. var = (square_sum - sum * sum / count) / count; \
  190. dst[y * dst_linesize + x] = (sigma * mean + var * src[y * src_linesize + x]) / (sigma + var); \
  191. } \
  192. } \
  193. return 0; \
  194. }
  195. FILTER_SLICE(uint8_t, byte)
  196. FILTER_SLICE(uint16_t, word)
  197. static int filter_frame(AVFilterLink *inlink, AVFrame *in)
  198. {
  199. AVFilterContext *ctx = inlink->dst;
  200. YAEPContext *s = ctx->priv;
  201. AVFilterLink *outlink = ctx->outputs[0];
  202. AVFrame *out;
  203. int plane;
  204. const int nb_threads = ff_filter_get_nb_threads(ctx);
  205. ThreadData td;
  206. if (av_frame_is_writable(in)) {
  207. out = in;
  208. } else {
  209. out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
  210. if (!out) {
  211. av_frame_free(&in);
  212. return AVERROR(ENOMEM);
  213. }
  214. av_frame_copy_props(out, in);
  215. }
  216. for (plane = 0; plane < s->nb_planes; plane++) {
  217. if (!s->radius || !(s->planes & (1<<plane))) {
  218. if (out != in) {
  219. av_image_copy_plane(out->data[plane], out->linesize[plane],
  220. in->data[plane], in->linesize[plane],
  221. s->planewidth[plane] * ((s->depth + 7) / 8),
  222. s->planeheight[plane]);
  223. }
  224. continue;
  225. }
  226. td.width = s->planewidth[plane];
  227. td.height = s->planeheight[plane];
  228. td.src = in->data[plane];
  229. td.src_linesize = in->linesize[plane];
  230. ctx->internal->execute(ctx, s->pre_calculate_row, &td, NULL, FFMIN(td.height, nb_threads));
  231. ctx->internal->execute(ctx, pre_calculate_col, &td, NULL, FFMIN(td.width, nb_threads));
  232. td.dst = out->data[plane];
  233. td.dst_linesize = out->linesize[plane];
  234. ctx->internal->execute(ctx, s->filter_slice, &td, NULL, FFMIN(td.height, nb_threads));
  235. }
  236. if (out != in)
  237. av_frame_free(&in);
  238. return ff_filter_frame(outlink, out);
  239. }
  240. static int config_input(AVFilterLink *inlink)
  241. {
  242. YAEPContext *s = inlink->dst->priv;
  243. const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
  244. s->depth = desc->comp[0].depth;
  245. s->planewidth[1] = s->planewidth[2] = AV_CEIL_RSHIFT(inlink->w, desc->log2_chroma_w);
  246. s->planewidth[0] = s->planewidth[3] = inlink->w;
  247. s->planeheight[1] = s->planeheight[2] = AV_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h);
  248. s->planeheight[0] = s->planeheight[3] = inlink->h;
  249. s->nb_planes = av_pix_fmt_count_planes(inlink->format);
  250. s->radius = FFMIN(s->radius, AV_CEIL_RSHIFT(FFMIN(inlink->w, inlink->h), 1));
  251. if (s->depth <= 8) {
  252. s->pre_calculate_row = pre_calculate_row_byte;
  253. s->filter_slice = filter_slice_byte;
  254. } else {
  255. s->pre_calculate_row = pre_calculate_row_word;
  256. s->filter_slice = filter_slice_word;
  257. }
  258. // padding one row on the top, and padding one col on the left, that is why + 1 below
  259. s->sat_linesize = inlink->w + 1;
  260. s->sat = av_mallocz_array(inlink->h + 1, s->sat_linesize*sizeof(*s->sat));
  261. if (!s->sat)
  262. return AVERROR(ENOMEM);
  263. s->square_sat = av_mallocz_array(inlink->h + 1, s->sat_linesize*sizeof(*s->square_sat));
  264. if (!s->square_sat)
  265. return AVERROR(ENOMEM);
  266. return 0;
  267. }
  268. static const AVFilterPad yaep_inputs[] = {
  269. {
  270. .name = "default",
  271. .type = AVMEDIA_TYPE_VIDEO,
  272. .config_props = config_input,
  273. .filter_frame = filter_frame,
  274. },
  275. { NULL }
  276. };
  277. static const AVFilterPad yaep_outputs[] = {
  278. {
  279. .name = "default",
  280. .type = AVMEDIA_TYPE_VIDEO,
  281. },
  282. { NULL }
  283. };
  284. #define OFFSET(x) offsetof(YAEPContext, x)
  285. #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_RUNTIME_PARAM
  286. static const AVOption yaepblur_options[] = {
  287. { "radius", "set window radius", OFFSET(radius), AV_OPT_TYPE_INT, {.i64=3}, 0, INT_MAX, .flags=FLAGS },
  288. { "r" , "set window radius", OFFSET(radius), AV_OPT_TYPE_INT, {.i64=3}, 0, INT_MAX, .flags=FLAGS },
  289. { "planes", "set planes to filter", OFFSET(planes), AV_OPT_TYPE_INT, {.i64=1}, 0, 0xF, .flags=FLAGS },
  290. { "p", "set planes to filter", OFFSET(planes), AV_OPT_TYPE_INT, {.i64=1}, 0, 0xF, .flags=FLAGS },
  291. { "sigma", "set blur strength", OFFSET(sigma), AV_OPT_TYPE_INT, {.i64=128}, 1, INT_MAX, .flags=FLAGS },
  292. { "s", "set blur strength", OFFSET(sigma), AV_OPT_TYPE_INT, {.i64=128}, 1, INT_MAX, .flags=FLAGS },
  293. { NULL }
  294. };
  295. AVFILTER_DEFINE_CLASS(yaepblur);
  296. AVFilter ff_vf_yaepblur = {
  297. .name = "yaepblur",
  298. .description = NULL_IF_CONFIG_SMALL("Yet another edge preserving blur filter."),
  299. .priv_size = sizeof(YAEPContext),
  300. .priv_class = &yaepblur_class,
  301. .uninit = uninit,
  302. .query_formats = query_formats,
  303. .inputs = yaep_inputs,
  304. .outputs = yaep_outputs,
  305. .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | AVFILTER_FLAG_SLICE_THREADS,
  306. .process_command = ff_filter_process_command,
  307. };