| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.filterpacks.videoproc; |
| |
| import android.filterfw.core.Filter; |
| import android.filterfw.core.FilterContext; |
| import android.filterfw.core.GenerateFieldPort; |
| import android.filterfw.core.GenerateFinalPort; |
| import android.filterfw.core.Frame; |
| import android.filterfw.core.GLFrame; |
| import android.filterfw.core.FrameFormat; |
| import android.filterfw.core.MutableFrameFormat; |
| import android.filterfw.core.ShaderProgram; |
| import android.filterfw.format.ImageFormat; |
| import android.opengl.GLES20; |
| import android.os.SystemClock; |
| import android.os.SystemProperties; |
| import android.util.Log; |
| |
| import java.lang.Math; |
| import java.util.Arrays; |
| import java.nio.ByteBuffer; |
| |
| /** |
| * @hide |
| */ |
| public class BackDropperFilter extends Filter { |
| /** User-visible parameters */ |
| |
| private final int BACKGROUND_STRETCH = 0; |
| private final int BACKGROUND_FIT = 1; |
| private final int BACKGROUND_FILL_CROP = 2; |
| |
| @GenerateFieldPort(name = "backgroundFitMode", hasDefault = true) |
| private int mBackgroundFitMode = BACKGROUND_FILL_CROP; |
| @GenerateFieldPort(name = "learningDuration", hasDefault = true) |
| private int mLearningDuration = DEFAULT_LEARNING_DURATION; |
| @GenerateFieldPort(name = "learningVerifyDuration", hasDefault = true) |
| private int mLearningVerifyDuration = DEFAULT_LEARNING_VERIFY_DURATION; |
| @GenerateFieldPort(name = "acceptStddev", hasDefault = true) |
| private float mAcceptStddev = DEFAULT_ACCEPT_STDDEV; |
| @GenerateFieldPort(name = "hierLrgScale", hasDefault = true) |
| private float mHierarchyLrgScale = DEFAULT_HIER_LRG_SCALE; |
| @GenerateFieldPort(name = "hierMidScale", hasDefault = true) |
| private float mHierarchyMidScale = DEFAULT_HIER_MID_SCALE; |
| @GenerateFieldPort(name = "hierSmlScale", hasDefault = true) |
| private float mHierarchySmlScale = DEFAULT_HIER_SML_SCALE; |
| |
| // Dimensions of foreground / background mask. Optimum value should take into account only |
| // image contents, NOT dimensions of input video stream. |
| @GenerateFieldPort(name = "maskWidthExp", hasDefault = true) |
| private int mMaskWidthExp = DEFAULT_MASK_WIDTH_EXPONENT; |
| @GenerateFieldPort(name = "maskHeightExp", hasDefault = true) |
| private int mMaskHeightExp = DEFAULT_MASK_HEIGHT_EXPONENT; |
| |
| // Levels at which to compute foreground / background decision. Think of them as are deltas |
| // SUBTRACTED from maskWidthExp and maskHeightExp. |
| @GenerateFieldPort(name = "hierLrgExp", hasDefault = true) |
| private int mHierarchyLrgExp = DEFAULT_HIER_LRG_EXPONENT; |
| @GenerateFieldPort(name = "hierMidExp", hasDefault = true) |
| private int mHierarchyMidExp = DEFAULT_HIER_MID_EXPONENT; |
| @GenerateFieldPort(name = "hierSmlExp", hasDefault = true) |
| private int mHierarchySmlExp = DEFAULT_HIER_SML_EXPONENT; |
| |
| @GenerateFieldPort(name = "lumScale", hasDefault = true) |
| private float mLumScale = DEFAULT_Y_SCALE_FACTOR; |
| @GenerateFieldPort(name = "chromaScale", hasDefault = true) |
| private float mChromaScale = DEFAULT_UV_SCALE_FACTOR; |
| @GenerateFieldPort(name = "maskBg", hasDefault = true) |
| private float mMaskBg = DEFAULT_MASK_BLEND_BG; |
| @GenerateFieldPort(name = "maskFg", hasDefault = true) |
| private float mMaskFg = DEFAULT_MASK_BLEND_FG; |
| @GenerateFieldPort(name = "exposureChange", hasDefault = true) |
| private float mExposureChange = DEFAULT_EXPOSURE_CHANGE; |
| @GenerateFieldPort(name = "whitebalanceredChange", hasDefault = true) |
| private float mWhiteBalanceRedChange = DEFAULT_WHITE_BALANCE_RED_CHANGE; |
| @GenerateFieldPort(name = "whitebalanceblueChange", hasDefault = true) |
| private float mWhiteBalanceBlueChange = DEFAULT_WHITE_BALANCE_BLUE_CHANGE; |
| @GenerateFieldPort(name = "autowbToggle", hasDefault = true) |
| private int mAutoWBToggle = DEFAULT_WHITE_BALANCE_TOGGLE; |
| |
| // TODO: These are not updatable: |
| @GenerateFieldPort(name = "learningAdaptRate", hasDefault = true) |
| private float mAdaptRateLearning = DEFAULT_LEARNING_ADAPT_RATE; |
| @GenerateFieldPort(name = "adaptRateBg", hasDefault = true) |
| private float mAdaptRateBg = DEFAULT_ADAPT_RATE_BG; |
| @GenerateFieldPort(name = "adaptRateFg", hasDefault = true) |
| private float mAdaptRateFg = DEFAULT_ADAPT_RATE_FG; |
| @GenerateFieldPort(name = "maskVerifyRate", hasDefault = true) |
| private float mVerifyRate = DEFAULT_MASK_VERIFY_RATE; |
| @GenerateFieldPort(name = "learningDoneListener", hasDefault = true) |
| private LearningDoneListener mLearningDoneListener = null; |
| |
| @GenerateFieldPort(name = "useTheForce", hasDefault = true) |
| private boolean mUseTheForce = false; |
| |
| @GenerateFinalPort(name = "provideDebugOutputs", hasDefault = true) |
| private boolean mProvideDebugOutputs = false; |
| |
| // Whether to mirror the background or not. For ex, the Camera app |
| // would mirror the preview for the front camera |
| @GenerateFieldPort(name = "mirrorBg", hasDefault = true) |
| private boolean mMirrorBg = false; |
| |
| // The orientation of the display. This will change the flipping |
| // coordinates, if we were to mirror the background |
| @GenerateFieldPort(name = "orientation", hasDefault = true) |
| private int mOrientation = 0; |
| |
| /** Default algorithm parameter values, for non-shader use */ |
| |
| // Frame count for learning bg model |
| private static final int DEFAULT_LEARNING_DURATION = 40; |
| // Frame count for learning verification |
| private static final int DEFAULT_LEARNING_VERIFY_DURATION = 10; |
| // Maximum distance (in standard deviations) for considering a pixel as background |
| private static final float DEFAULT_ACCEPT_STDDEV = 0.85f; |
| // Variance threshold scale factor for large scale of hierarchy |
| private static final float DEFAULT_HIER_LRG_SCALE = 0.7f; |
| // Variance threshold scale factor for medium scale of hierarchy |
| private static final float DEFAULT_HIER_MID_SCALE = 0.6f; |
| // Variance threshold scale factor for small scale of hierarchy |
| private static final float DEFAULT_HIER_SML_SCALE = 0.5f; |
| // Width of foreground / background mask. |
| private static final int DEFAULT_MASK_WIDTH_EXPONENT = 8; |
| // Height of foreground / background mask. |
| private static final int DEFAULT_MASK_HEIGHT_EXPONENT = 8; |
| // Area over which to average for large scale (length in pixels = 2^HIERARCHY_*_EXPONENT) |
| private static final int DEFAULT_HIER_LRG_EXPONENT = 3; |
| // Area over which to average for medium scale |
| private static final int DEFAULT_HIER_MID_EXPONENT = 2; |
| // Area over which to average for small scale |
| private static final int DEFAULT_HIER_SML_EXPONENT = 0; |
| // Scale factor for luminance channel in distance calculations (larger = more significant) |
| private static final float DEFAULT_Y_SCALE_FACTOR = 0.40f; |
| // Scale factor for chroma channels in distance calculations |
| private static final float DEFAULT_UV_SCALE_FACTOR = 1.35f; |
| // Mask value to start blending away from background |
| private static final float DEFAULT_MASK_BLEND_BG = 0.65f; |
| // Mask value to start blending away from foreground |
| private static final float DEFAULT_MASK_BLEND_FG = 0.95f; |
| // Exposure stop number to change the brightness of foreground |
| private static final float DEFAULT_EXPOSURE_CHANGE = 1.0f; |
| // White balance change in Red channel for foreground |
| private static final float DEFAULT_WHITE_BALANCE_RED_CHANGE = 0.0f; |
| // White balance change in Blue channel for foreground |
| private static final float DEFAULT_WHITE_BALANCE_BLUE_CHANGE = 0.0f; |
| // Variable to control automatic white balance effect |
| // 0.f -> Auto WB is off; 1.f-> Auto WB is on |
| private static final int DEFAULT_WHITE_BALANCE_TOGGLE = 0; |
| |
| // Default rate at which to learn bg model during learning period |
| private static final float DEFAULT_LEARNING_ADAPT_RATE = 0.2f; |
| // Default rate at which to learn bg model from new background pixels |
| private static final float DEFAULT_ADAPT_RATE_BG = 0.0f; |
| // Default rate at which to learn bg model from new foreground pixels |
| private static final float DEFAULT_ADAPT_RATE_FG = 0.0f; |
| // Default rate at which to verify whether background is stable |
| private static final float DEFAULT_MASK_VERIFY_RATE = 0.25f; |
| // Default rate at which to verify whether background is stable |
| private static final int DEFAULT_LEARNING_DONE_THRESHOLD = 20; |
| |
| // Default 3x3 matrix, column major, for fitting background 1:1 |
| private static final float[] DEFAULT_BG_FIT_TRANSFORM = new float[] { |
| 1.0f, 0.0f, 0.0f, |
| 0.0f, 1.0f, 0.0f, |
| 0.0f, 0.0f, 1.0f |
| }; |
| |
| /** Default algorithm parameter values, for shader use */ |
| |
| // Area over which to blur binary mask values (length in pixels = 2^MASK_SMOOTH_EXPONENT) |
| private static final String MASK_SMOOTH_EXPONENT = "2.0"; |
| // Scale value for mapping variance distance to fit nicely to 0-1, 8-bit |
| private static final String DISTANCE_STORAGE_SCALE = "0.6"; |
| // Scale value for mapping variance to fit nicely to 0-1, 8-bit |
| private static final String VARIANCE_STORAGE_SCALE = "5.0"; |
| // Default scale of auto white balance parameters |
| private static final String DEFAULT_AUTO_WB_SCALE = "0.25"; |
| // Minimum variance (0-255 scale) |
| private static final String MIN_VARIANCE = "3.0"; |
| // Column-major array for 4x4 matrix converting RGB to YCbCr, JPEG definition (no pedestal) |
| private static final String RGB_TO_YUV_MATRIX = "0.299, -0.168736, 0.5, 0.000, " + |
| "0.587, -0.331264, -0.418688, 0.000, " + |
| "0.114, 0.5, -0.081312, 0.000, " + |
| "0.000, 0.5, 0.5, 1.000 "; |
| /** Stream names */ |
| |
| private static final String[] mInputNames = {"video", |
| "background"}; |
| |
| private static final String[] mOutputNames = {"video"}; |
| |
| private static final String[] mDebugOutputNames = {"debug1", |
| "debug2"}; |
| |
| /** Other private variables */ |
| |
| private FrameFormat mOutputFormat; |
| private MutableFrameFormat mMemoryFormat; |
| private MutableFrameFormat mMaskFormat; |
| private MutableFrameFormat mAverageFormat; |
| |
| private final boolean mLogVerbose; |
| private static final String TAG = "BackDropperFilter"; |
| |
| /** Shader source code */ |
| |
| // Shared uniforms and utility functions |
| private static String mSharedUtilShader = |
| "precision mediump float;\n" + |
| "uniform float fg_adapt_rate;\n" + |
| "uniform float bg_adapt_rate;\n" + |
| "const mat4 coeff_yuv = mat4(" + RGB_TO_YUV_MATRIX + ");\n" + |
| "const float dist_scale = " + DISTANCE_STORAGE_SCALE + ";\n" + |
| "const float inv_dist_scale = 1. / dist_scale;\n" + |
| "const float var_scale=" + VARIANCE_STORAGE_SCALE + ";\n" + |
| "const float inv_var_scale = 1. / var_scale;\n" + |
| "const float min_variance = inv_var_scale *" + MIN_VARIANCE + "/ 256.;\n" + |
| "const float auto_wb_scale = " + DEFAULT_AUTO_WB_SCALE + ";\n" + |
| "\n" + |
| // Variance distance in luminance between current pixel and background model |
| "float gauss_dist_y(float y, float mean, float variance) {\n" + |
| " float dist = (y - mean) * (y - mean) / variance;\n" + |
| " return dist;\n" + |
| "}\n" + |
| // Sum of variance distances in chroma between current pixel and background |
| // model |
| "float gauss_dist_uv(vec2 uv, vec2 mean, vec2 variance) {\n" + |
| " vec2 dist = (uv - mean) * (uv - mean) / variance;\n" + |
| " return dist.r + dist.g;\n" + |
| "}\n" + |
| // Select learning rate for pixel based on smoothed decision mask alpha |
| "float local_adapt_rate(float alpha) {\n" + |
| " return mix(bg_adapt_rate, fg_adapt_rate, alpha);\n" + |
| "}\n" + |
| "\n"; |
| |
| // Distance calculation shader. Calculates a distance metric between the foreground and the |
| // current background model, in both luminance and in chroma (yuv space). Distance is |
| // measured in variances from the mean background value. For chroma, the distance is the sum |
| // of the two individual color channel distances. The distances are output on the b and alpha |
| // channels, r and g are for debug information. |
| // Inputs: |
| // tex_sampler_0: Mip-map for foreground (live) video frame. |
| // tex_sampler_1: Background mean mask. |
| // tex_sampler_2: Background variance mask. |
| // subsample_level: Level on foreground frame's mip-map. |
| private static final String mBgDistanceShader = |
| "uniform sampler2D tex_sampler_0;\n" + |
| "uniform sampler2D tex_sampler_1;\n" + |
| "uniform sampler2D tex_sampler_2;\n" + |
| "uniform float subsample_level;\n" + |
| "varying vec2 v_texcoord;\n" + |
| "void main() {\n" + |
| " vec4 fg_rgb = texture2D(tex_sampler_0, v_texcoord, subsample_level);\n" + |
| " vec4 fg = coeff_yuv * vec4(fg_rgb.rgb, 1.);\n" + |
| " vec4 mean = texture2D(tex_sampler_1, v_texcoord);\n" + |
| " vec4 variance = inv_var_scale * texture2D(tex_sampler_2, v_texcoord);\n" + |
| "\n" + |
| " float dist_y = gauss_dist_y(fg.r, mean.r, variance.r);\n" + |
| " float dist_uv = gauss_dist_uv(fg.gb, mean.gb, variance.gb);\n" + |
| " gl_FragColor = vec4(0.5*fg.rg, dist_scale*dist_y, dist_scale*dist_uv);\n" + |
| "}\n"; |
| |
| // Foreground/background mask decision shader. Decides whether a frame is in the foreground or |
| // the background using a hierarchical threshold on the distance. Binary foreground/background |
| // mask is placed in the alpha channel. The RGB channels contain debug information. |
| private static final String mBgMaskShader = |
| "uniform sampler2D tex_sampler_0;\n" + |
| "uniform float accept_variance;\n" + |
| "uniform vec2 yuv_weights;\n" + |
| "uniform float scale_lrg;\n" + |
| "uniform float scale_mid;\n" + |
| "uniform float scale_sml;\n" + |
| "uniform float exp_lrg;\n" + |
| "uniform float exp_mid;\n" + |
| "uniform float exp_sml;\n" + |
| "varying vec2 v_texcoord;\n" + |
| // Decide whether pixel is foreground or background based on Y and UV |
| // distance and maximum acceptable variance. |
| // yuv_weights.x is smaller than yuv_weights.y to discount the influence of shadow |
| "bool is_fg(vec2 dist_yc, float accept_variance) {\n" + |
| " return ( dot(yuv_weights, dist_yc) >= accept_variance );\n" + |
| "}\n" + |
| "void main() {\n" + |
| " vec4 dist_lrg_sc = texture2D(tex_sampler_0, v_texcoord, exp_lrg);\n" + |
| " vec4 dist_mid_sc = texture2D(tex_sampler_0, v_texcoord, exp_mid);\n" + |
| " vec4 dist_sml_sc = texture2D(tex_sampler_0, v_texcoord, exp_sml);\n" + |
| " vec2 dist_lrg = inv_dist_scale * dist_lrg_sc.ba;\n" + |
| " vec2 dist_mid = inv_dist_scale * dist_mid_sc.ba;\n" + |
| " vec2 dist_sml = inv_dist_scale * dist_sml_sc.ba;\n" + |
| " vec2 norm_dist = 0.75 * dist_sml / accept_variance;\n" + // For debug viz |
| " bool is_fg_lrg = is_fg(dist_lrg, accept_variance * scale_lrg);\n" + |
| " bool is_fg_mid = is_fg_lrg || is_fg(dist_mid, accept_variance * scale_mid);\n" + |
| " float is_fg_sml =\n" + |
| " float(is_fg_mid || is_fg(dist_sml, accept_variance * scale_sml));\n" + |
| " float alpha = 0.5 * is_fg_sml + 0.3 * float(is_fg_mid) + 0.2 * float(is_fg_lrg);\n" + |
| " gl_FragColor = vec4(alpha, norm_dist, is_fg_sml);\n" + |
| "}\n"; |
| |
| // Automatic White Balance parameter decision shader |
| // Use the Gray World assumption that in a white balance corrected image, the average of R, G, B |
| // channel will be a common gray value. |
| // To match the white balance of foreground and background, the average of R, G, B channel of |
| // two videos should match. |
| // Inputs: |
| // tex_sampler_0: Mip-map for foreground (live) video frame. |
| // tex_sampler_1: Mip-map for background (playback) video frame. |
| // pyramid_depth: Depth of input frames' mip-maps. |
| private static final String mAutomaticWhiteBalance = |
| "uniform sampler2D tex_sampler_0;\n" + |
| "uniform sampler2D tex_sampler_1;\n" + |
| "uniform float pyramid_depth;\n" + |
| "uniform bool autowb_toggle;\n" + |
| "varying vec2 v_texcoord;\n" + |
| "void main() {\n" + |
| " vec4 mean_video = texture2D(tex_sampler_0, v_texcoord, pyramid_depth);\n"+ |
| " vec4 mean_bg = texture2D(tex_sampler_1, v_texcoord, pyramid_depth);\n" + |
| // If Auto WB is toggled off, the return texture will be a unicolor texture of value 1 |
| // If Auto WB is toggled on, the return texture will be a unicolor texture with |
| // adjustment parameters for R and B channels stored in the corresponding channel |
| " float green_normalizer = mean_video.g / mean_bg.g;\n"+ |
| " vec4 adjusted_value = vec4(mean_bg.r / mean_video.r * green_normalizer, 1., \n" + |
| " mean_bg.b / mean_video.b * green_normalizer, 1.) * auto_wb_scale; \n" + |
| " gl_FragColor = autowb_toggle ? adjusted_value : vec4(auto_wb_scale);\n" + |
| "}\n"; |
| |
| |
| // Background subtraction shader. Uses a mipmap of the binary mask map to blend smoothly between |
| // foreground and background |
| // Inputs: |
| // tex_sampler_0: Foreground (live) video frame. |
| // tex_sampler_1: Background (playback) video frame. |
| // tex_sampler_2: Foreground/background mask. |
| // tex_sampler_3: Auto white-balance factors. |
| private static final String mBgSubtractShader = |
| "uniform mat3 bg_fit_transform;\n" + |
| "uniform float mask_blend_bg;\n" + |
| "uniform float mask_blend_fg;\n" + |
| "uniform float exposure_change;\n" + |
| "uniform float whitebalancered_change;\n" + |
| "uniform float whitebalanceblue_change;\n" + |
| "uniform sampler2D tex_sampler_0;\n" + |
| "uniform sampler2D tex_sampler_1;\n" + |
| "uniform sampler2D tex_sampler_2;\n" + |
| "uniform sampler2D tex_sampler_3;\n" + |
| "varying vec2 v_texcoord;\n" + |
| "void main() {\n" + |
| " vec2 bg_texcoord = (bg_fit_transform * vec3(v_texcoord, 1.)).xy;\n" + |
| " vec4 bg_rgb = texture2D(tex_sampler_1, bg_texcoord);\n" + |
| // The foreground texture is modified by multiplying both manual and auto white balance changes in R and B |
| // channel and multiplying exposure change in all R, G, B channels. |
| " vec4 wb_auto_scale = texture2D(tex_sampler_3, v_texcoord) * exposure_change / auto_wb_scale;\n" + |
| " vec4 wb_manual_scale = vec4(1. + whitebalancered_change, 1., 1. + whitebalanceblue_change, 1.);\n" + |
| " vec4 fg_rgb = texture2D(tex_sampler_0, v_texcoord);\n" + |
| " vec4 fg_adjusted = fg_rgb * wb_manual_scale * wb_auto_scale;\n"+ |
| " vec4 mask = texture2D(tex_sampler_2, v_texcoord, \n" + |
| " " + MASK_SMOOTH_EXPONENT + ");\n" + |
| " float alpha = smoothstep(mask_blend_bg, mask_blend_fg, mask.a);\n" + |
| " gl_FragColor = mix(bg_rgb, fg_adjusted, alpha);\n"; |
| |
| // May the Force... Makes the foreground object translucent blue, with a bright |
| // blue-white outline |
| private static final String mBgSubtractForceShader = |
| " vec4 ghost_rgb = (fg_adjusted * 0.7 + vec4(0.3,0.3,0.4,0.))*0.65 + \n" + |
| " 0.35*bg_rgb;\n" + |
| " float glow_start = 0.75 * mask_blend_bg; \n"+ |
| " float glow_max = mask_blend_bg; \n"+ |
| " gl_FragColor = mask.a < glow_start ? bg_rgb : \n" + |
| " mask.a < glow_max ? mix(bg_rgb, vec4(0.9,0.9,1.0,1.0), \n" + |
| " (mask.a - glow_start) / (glow_max - glow_start) ) : \n" + |
| " mask.a < mask_blend_fg ? mix(vec4(0.9,0.9,1.0,1.0), ghost_rgb, \n" + |
| " (mask.a - glow_max) / (mask_blend_fg - glow_max) ) : \n" + |
| " ghost_rgb;\n" + |
| "}\n"; |
| |
| // Background model mean update shader. Skews the current model mean toward the most recent pixel |
| // value for a pixel, weighted by the learning rate and by whether the pixel is classified as |
| // foreground or background. |
| // Inputs: |
| // tex_sampler_0: Mip-map for foreground (live) video frame. |
| // tex_sampler_1: Background mean mask. |
| // tex_sampler_2: Foreground/background mask. |
| // subsample_level: Level on foreground frame's mip-map. |
| private static final String mUpdateBgModelMeanShader = |
| "uniform sampler2D tex_sampler_0;\n" + |
| "uniform sampler2D tex_sampler_1;\n" + |
| "uniform sampler2D tex_sampler_2;\n" + |
| "uniform float subsample_level;\n" + |
| "varying vec2 v_texcoord;\n" + |
| "void main() {\n" + |
| " vec4 fg_rgb = texture2D(tex_sampler_0, v_texcoord, subsample_level);\n" + |
| " vec4 fg = coeff_yuv * vec4(fg_rgb.rgb, 1.);\n" + |
| " vec4 mean = texture2D(tex_sampler_1, v_texcoord);\n" + |
| " vec4 mask = texture2D(tex_sampler_2, v_texcoord, \n" + |
| " " + MASK_SMOOTH_EXPONENT + ");\n" + |
| "\n" + |
| " float alpha = local_adapt_rate(mask.a);\n" + |
| " vec4 new_mean = mix(mean, fg, alpha);\n" + |
| " gl_FragColor = new_mean;\n" + |
| "}\n"; |
| |
| // Background model variance update shader. Skews the current model variance toward the most |
| // recent variance for the pixel, weighted by the learning rate and by whether the pixel is |
| // classified as foreground or background. |
| // Inputs: |
| // tex_sampler_0: Mip-map for foreground (live) video frame. |
| // tex_sampler_1: Background mean mask. |
| // tex_sampler_2: Background variance mask. |
| // tex_sampler_3: Foreground/background mask. |
| // subsample_level: Level on foreground frame's mip-map. |
| // TODO: to improve efficiency, use single mark for mean + variance, then merge this into |
| // mUpdateBgModelMeanShader. |
| private static final String mUpdateBgModelVarianceShader = |
| "uniform sampler2D tex_sampler_0;\n" + |
| "uniform sampler2D tex_sampler_1;\n" + |
| "uniform sampler2D tex_sampler_2;\n" + |
| "uniform sampler2D tex_sampler_3;\n" + |
| "uniform float subsample_level;\n" + |
| "varying vec2 v_texcoord;\n" + |
| "void main() {\n" + |
| " vec4 fg_rgb = texture2D(tex_sampler_0, v_texcoord, subsample_level);\n" + |
| " vec4 fg = coeff_yuv * vec4(fg_rgb.rgb, 1.);\n" + |
| " vec4 mean = texture2D(tex_sampler_1, v_texcoord);\n" + |
| " vec4 variance = inv_var_scale * texture2D(tex_sampler_2, v_texcoord);\n" + |
| " vec4 mask = texture2D(tex_sampler_3, v_texcoord, \n" + |
| " " + MASK_SMOOTH_EXPONENT + ");\n" + |
| "\n" + |
| " float alpha = local_adapt_rate(mask.a);\n" + |
| " vec4 cur_variance = (fg-mean)*(fg-mean);\n" + |
| " vec4 new_variance = mix(variance, cur_variance, alpha);\n" + |
| " new_variance = max(new_variance, vec4(min_variance));\n" + |
| " gl_FragColor = var_scale * new_variance;\n" + |
| "}\n"; |
| |
| // Background verification shader. Skews the current background verification mask towards the |
| // most recent frame, weighted by the learning rate. |
| private static final String mMaskVerifyShader = |
| "uniform sampler2D tex_sampler_0;\n" + |
| "uniform sampler2D tex_sampler_1;\n" + |
| "uniform float verify_rate;\n" + |
| "varying vec2 v_texcoord;\n" + |
| "void main() {\n" + |
| " vec4 lastmask = texture2D(tex_sampler_0, v_texcoord);\n" + |
| " vec4 mask = texture2D(tex_sampler_1, v_texcoord);\n" + |
| " float newmask = mix(lastmask.a, mask.a, verify_rate);\n" + |
| " gl_FragColor = vec4(0., 0., 0., newmask);\n" + |
| "}\n"; |
| |
| /** Shader program objects */ |
| |
| private ShaderProgram mBgDistProgram; |
| private ShaderProgram mBgMaskProgram; |
| private ShaderProgram mBgSubtractProgram; |
| private ShaderProgram mBgUpdateMeanProgram; |
| private ShaderProgram mBgUpdateVarianceProgram; |
| private ShaderProgram mCopyOutProgram; |
| private ShaderProgram mAutomaticWhiteBalanceProgram; |
| private ShaderProgram mMaskVerifyProgram; |
| private ShaderProgram copyShaderProgram; |
| |
| /** Background model storage */ |
| |
| private boolean mPingPong; |
| private GLFrame mBgMean[]; |
| private GLFrame mBgVariance[]; |
| private GLFrame mMaskVerify[]; |
| private GLFrame mDistance; |
| private GLFrame mAutoWB; |
| private GLFrame mMask; |
| private GLFrame mVideoInput; |
| private GLFrame mBgInput; |
| private GLFrame mMaskAverage; |
| |
| /** Overall filter state */ |
| |
| private boolean isOpen; |
| private int mFrameCount; |
| private boolean mStartLearning; |
| private boolean mBackgroundFitModeChanged; |
| private float mRelativeAspect; |
| private int mPyramidDepth; |
| private int mSubsampleLevel; |
| |
| /** Learning listener object */ |
| |
| public interface LearningDoneListener { |
| public void onLearningDone(BackDropperFilter filter); |
| } |
| |
| /** Public Filter methods */ |
| |
| public BackDropperFilter(String name) { |
| super(name); |
| |
| mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); |
| |
| String adjStr = SystemProperties.get("ro.media.effect.bgdropper.adj"); |
| if (adjStr.length() > 0) { |
| try { |
| mAcceptStddev += Float.parseFloat(adjStr); |
| if (mLogVerbose) { |
| Log.v(TAG, "Adjusting accept threshold by " + adjStr + |
| ", now " + mAcceptStddev); |
| } |
| } catch (NumberFormatException e) { |
| Log.e(TAG, |
| "Badly formatted property ro.media.effect.bgdropper.adj: " + adjStr); |
| } |
| } |
| } |
| |
| @Override |
| public void setupPorts() { |
| // Inputs. |
| // TODO: Target should be GPU, but relaxed for now. |
| FrameFormat imageFormat = ImageFormat.create(ImageFormat.COLORSPACE_RGBA, |
| FrameFormat.TARGET_UNSPECIFIED); |
| for (String inputName : mInputNames) { |
| addMaskedInputPort(inputName, imageFormat); |
| } |
| // Normal outputs |
| for (String outputName : mOutputNames) { |
| addOutputBasedOnInput(outputName, "video"); |
| } |
| |
| // Debug outputs |
| if (mProvideDebugOutputs) { |
| for (String outputName : mDebugOutputNames) { |
| addOutputBasedOnInput(outputName, "video"); |
| } |
| } |
| } |
| |
| @Override |
| public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { |
| // Create memory format based on video input. |
| MutableFrameFormat format = inputFormat.mutableCopy(); |
| // Is this a debug output port? If so, leave dimensions unspecified. |
| if (!Arrays.asList(mOutputNames).contains(portName)) { |
| format.setDimensions(FrameFormat.SIZE_UNSPECIFIED, FrameFormat.SIZE_UNSPECIFIED); |
| } |
| return format; |
| } |
| |
| private boolean createMemoryFormat(FrameFormat inputFormat) { |
| // We can't resize because that would require re-learning. |
| if (mMemoryFormat != null) { |
| return false; |
| } |
| |
| if (inputFormat.getWidth() == FrameFormat.SIZE_UNSPECIFIED || |
| inputFormat.getHeight() == FrameFormat.SIZE_UNSPECIFIED) { |
| throw new RuntimeException("Attempting to process input frame with unknown size"); |
| } |
| |
| mMaskFormat = inputFormat.mutableCopy(); |
| int maskWidth = (int)Math.pow(2, mMaskWidthExp); |
| int maskHeight = (int)Math.pow(2, mMaskHeightExp); |
| mMaskFormat.setDimensions(maskWidth, maskHeight); |
| |
| mPyramidDepth = Math.max(mMaskWidthExp, mMaskHeightExp); |
| mMemoryFormat = mMaskFormat.mutableCopy(); |
| int widthExp = Math.max(mMaskWidthExp, pyramidLevel(inputFormat.getWidth())); |
| int heightExp = Math.max(mMaskHeightExp, pyramidLevel(inputFormat.getHeight())); |
| mPyramidDepth = Math.max(widthExp, heightExp); |
| int memWidth = Math.max(maskWidth, (int)Math.pow(2, widthExp)); |
| int memHeight = Math.max(maskHeight, (int)Math.pow(2, heightExp)); |
| mMemoryFormat.setDimensions(memWidth, memHeight); |
| mSubsampleLevel = mPyramidDepth - Math.max(mMaskWidthExp, mMaskHeightExp); |
| |
| if (mLogVerbose) { |
| Log.v(TAG, "Mask frames size " + maskWidth + " x " + maskHeight); |
| Log.v(TAG, "Pyramid levels " + widthExp + " x " + heightExp); |
| Log.v(TAG, "Memory frames size " + memWidth + " x " + memHeight); |
| } |
| |
| mAverageFormat = inputFormat.mutableCopy(); |
| mAverageFormat.setDimensions(1,1); |
| return true; |
| } |
| |
| public void prepare(FilterContext context){ |
| if (mLogVerbose) Log.v(TAG, "Preparing BackDropperFilter!"); |
| |
| mBgMean = new GLFrame[2]; |
| mBgVariance = new GLFrame[2]; |
| mMaskVerify = new GLFrame[2]; |
| copyShaderProgram = ShaderProgram.createIdentity(context); |
| } |
| |
| private void allocateFrames(FrameFormat inputFormat, FilterContext context) { |
| if (!createMemoryFormat(inputFormat)) { |
| return; // All set. |
| } |
| if (mLogVerbose) Log.v(TAG, "Allocating BackDropperFilter frames"); |
| |
| // Create initial background model values |
| int numBytes = mMaskFormat.getSize(); |
| byte[] initialBgMean = new byte[numBytes]; |
| byte[] initialBgVariance = new byte[numBytes]; |
| byte[] initialMaskVerify = new byte[numBytes]; |
| for (int i = 0; i < numBytes; i++) { |
| initialBgMean[i] = (byte)128; |
| initialBgVariance[i] = (byte)10; |
| initialMaskVerify[i] = (byte)0; |
| } |
| |
| // Get frames to store background model in |
| for (int i = 0; i < 2; i++) { |
| mBgMean[i] = (GLFrame)context.getFrameManager().newFrame(mMaskFormat); |
| mBgMean[i].setData(initialBgMean, 0, numBytes); |
| |
| mBgVariance[i] = (GLFrame)context.getFrameManager().newFrame(mMaskFormat); |
| mBgVariance[i].setData(initialBgVariance, 0, numBytes); |
| |
| mMaskVerify[i] = (GLFrame)context.getFrameManager().newFrame(mMaskFormat); |
| mMaskVerify[i].setData(initialMaskVerify, 0, numBytes); |
| } |
| |
| // Get frames to store other textures in |
| if (mLogVerbose) Log.v(TAG, "Done allocating texture for Mean and Variance objects!"); |
| |
| mDistance = (GLFrame)context.getFrameManager().newFrame(mMaskFormat); |
| mMask = (GLFrame)context.getFrameManager().newFrame(mMaskFormat); |
| mAutoWB = (GLFrame)context.getFrameManager().newFrame(mAverageFormat); |
| mVideoInput = (GLFrame)context.getFrameManager().newFrame(mMemoryFormat); |
| mBgInput = (GLFrame)context.getFrameManager().newFrame(mMemoryFormat); |
| mMaskAverage = (GLFrame)context.getFrameManager().newFrame(mAverageFormat); |
| |
| // Create shader programs |
| mBgDistProgram = new ShaderProgram(context, mSharedUtilShader + mBgDistanceShader); |
| mBgDistProgram.setHostValue("subsample_level", (float)mSubsampleLevel); |
| |
| mBgMaskProgram = new ShaderProgram(context, mSharedUtilShader + mBgMaskShader); |
| mBgMaskProgram.setHostValue("accept_variance", mAcceptStddev * mAcceptStddev); |
| float[] yuvWeights = { mLumScale, mChromaScale }; |
| mBgMaskProgram.setHostValue("yuv_weights", yuvWeights ); |
| mBgMaskProgram.setHostValue("scale_lrg", mHierarchyLrgScale); |
| mBgMaskProgram.setHostValue("scale_mid", mHierarchyMidScale); |
| mBgMaskProgram.setHostValue("scale_sml", mHierarchySmlScale); |
| mBgMaskProgram.setHostValue("exp_lrg", (float)(mSubsampleLevel + mHierarchyLrgExp)); |
| mBgMaskProgram.setHostValue("exp_mid", (float)(mSubsampleLevel + mHierarchyMidExp)); |
| mBgMaskProgram.setHostValue("exp_sml", (float)(mSubsampleLevel + mHierarchySmlExp)); |
| |
| if (mUseTheForce) { |
| mBgSubtractProgram = new ShaderProgram(context, mSharedUtilShader + mBgSubtractShader + mBgSubtractForceShader); |
| } else { |
| mBgSubtractProgram = new ShaderProgram(context, mSharedUtilShader + mBgSubtractShader + "}\n"); |
| } |
| mBgSubtractProgram.setHostValue("bg_fit_transform", DEFAULT_BG_FIT_TRANSFORM); |
| mBgSubtractProgram.setHostValue("mask_blend_bg", mMaskBg); |
| mBgSubtractProgram.setHostValue("mask_blend_fg", mMaskFg); |
| mBgSubtractProgram.setHostValue("exposure_change", mExposureChange); |
| mBgSubtractProgram.setHostValue("whitebalanceblue_change", mWhiteBalanceBlueChange); |
| mBgSubtractProgram.setHostValue("whitebalancered_change", mWhiteBalanceRedChange); |
| |
| |
| mBgUpdateMeanProgram = new ShaderProgram(context, mSharedUtilShader + mUpdateBgModelMeanShader); |
| mBgUpdateMeanProgram.setHostValue("subsample_level", (float)mSubsampleLevel); |
| |
| mBgUpdateVarianceProgram = new ShaderProgram(context, mSharedUtilShader + mUpdateBgModelVarianceShader); |
| mBgUpdateVarianceProgram.setHostValue("subsample_level", (float)mSubsampleLevel); |
| |
| mCopyOutProgram = ShaderProgram.createIdentity(context); |
| |
| mAutomaticWhiteBalanceProgram = new ShaderProgram(context, mSharedUtilShader + mAutomaticWhiteBalance); |
| mAutomaticWhiteBalanceProgram.setHostValue("pyramid_depth", (float)mPyramidDepth); |
| mAutomaticWhiteBalanceProgram.setHostValue("autowb_toggle", mAutoWBToggle); |
| |
| mMaskVerifyProgram = new ShaderProgram(context, mSharedUtilShader + mMaskVerifyShader); |
| mMaskVerifyProgram.setHostValue("verify_rate", mVerifyRate); |
| |
| if (mLogVerbose) Log.v(TAG, "Shader width set to " + mMemoryFormat.getWidth()); |
| |
| mRelativeAspect = 1.f; |
| |
| mFrameCount = 0; |
| mStartLearning = true; |
| } |
| |
| public void process(FilterContext context) { |
| // Grab inputs and ready intermediate frames and outputs. |
| Frame video = pullInput("video"); |
| Frame background = pullInput("background"); |
| allocateFrames(video.getFormat(), context); |
| |
| // Update learning rate after initial learning period |
| if (mStartLearning) { |
| if (mLogVerbose) Log.v(TAG, "Starting learning"); |
| mBgUpdateMeanProgram.setHostValue("bg_adapt_rate", mAdaptRateLearning); |
| mBgUpdateMeanProgram.setHostValue("fg_adapt_rate", mAdaptRateLearning); |
| mBgUpdateVarianceProgram.setHostValue("bg_adapt_rate", mAdaptRateLearning); |
| mBgUpdateVarianceProgram.setHostValue("fg_adapt_rate", mAdaptRateLearning); |
| mFrameCount = 0; |
| } |
| |
| // Select correct pingpong buffers |
| int inputIndex = mPingPong ? 0 : 1; |
| int outputIndex = mPingPong ? 1 : 0; |
| mPingPong = !mPingPong; |
| |
| // Check relative aspect ratios |
| updateBgScaling(video, background, mBackgroundFitModeChanged); |
| mBackgroundFitModeChanged = false; |
| |
| // Make copies for input frames to GLFrames |
| |
| copyShaderProgram.process(video, mVideoInput); |
| copyShaderProgram.process(background, mBgInput); |
| |
| mVideoInput.generateMipMap(); |
| mVideoInput.setTextureParameter(GLES20.GL_TEXTURE_MIN_FILTER, |
| GLES20.GL_LINEAR_MIPMAP_NEAREST); |
| |
| mBgInput.generateMipMap(); |
| mBgInput.setTextureParameter(GLES20.GL_TEXTURE_MIN_FILTER, |
| GLES20.GL_LINEAR_MIPMAP_NEAREST); |
| |
| if (mStartLearning) { |
| copyShaderProgram.process(mVideoInput, mBgMean[inputIndex]); |
| mStartLearning = false; |
| } |
| |
| // Process shaders |
| Frame[] distInputs = { mVideoInput, mBgMean[inputIndex], mBgVariance[inputIndex] }; |
| mBgDistProgram.process(distInputs, mDistance); |
| mDistance.generateMipMap(); |
| mDistance.setTextureParameter(GLES20.GL_TEXTURE_MIN_FILTER, |
| GLES20.GL_LINEAR_MIPMAP_NEAREST); |
| |
| mBgMaskProgram.process(mDistance, mMask); |
| mMask.generateMipMap(); |
| mMask.setTextureParameter(GLES20.GL_TEXTURE_MIN_FILTER, |
| GLES20.GL_LINEAR_MIPMAP_NEAREST); |
| |
| Frame[] autoWBInputs = { mVideoInput, mBgInput }; |
| mAutomaticWhiteBalanceProgram.process(autoWBInputs, mAutoWB); |
| |
| if (mFrameCount <= mLearningDuration) { |
| // During learning |
| pushOutput("video", video); |
| |
| if (mFrameCount == mLearningDuration - mLearningVerifyDuration) { |
| copyShaderProgram.process(mMask, mMaskVerify[outputIndex]); |
| |
| mBgUpdateMeanProgram.setHostValue("bg_adapt_rate", mAdaptRateBg); |
| mBgUpdateMeanProgram.setHostValue("fg_adapt_rate", mAdaptRateFg); |
| mBgUpdateVarianceProgram.setHostValue("bg_adapt_rate", mAdaptRateBg); |
| mBgUpdateVarianceProgram.setHostValue("fg_adapt_rate", mAdaptRateFg); |
| |
| |
| } else if (mFrameCount > mLearningDuration - mLearningVerifyDuration) { |
| // In the learning verification stage, compute background masks and a weighted average |
| // with weights grow exponentially with time |
| Frame[] maskVerifyInputs = {mMaskVerify[inputIndex], mMask}; |
| mMaskVerifyProgram.process(maskVerifyInputs, mMaskVerify[outputIndex]); |
| mMaskVerify[outputIndex].generateMipMap(); |
| mMaskVerify[outputIndex].setTextureParameter(GLES20.GL_TEXTURE_MIN_FILTER, |
| GLES20.GL_LINEAR_MIPMAP_NEAREST); |
| } |
| |
| if (mFrameCount == mLearningDuration) { |
| // In the last verification frame, verify if the verification mask is almost blank |
| // If not, restart learning |
| copyShaderProgram.process(mMaskVerify[outputIndex], mMaskAverage); |
| ByteBuffer mMaskAverageByteBuffer = mMaskAverage.getData(); |
| byte[] mask_average = mMaskAverageByteBuffer.array(); |
| int bi = (int)(mask_average[3] & 0xFF); |
| |
| if (mLogVerbose) { |
| Log.v(TAG, |
| String.format("Mask_average is %d, threshold is %d", |
| bi, DEFAULT_LEARNING_DONE_THRESHOLD)); |
| } |
| |
| if (bi >= DEFAULT_LEARNING_DONE_THRESHOLD) { |
| mStartLearning = true; // Restart learning |
| } else { |
| if (mLogVerbose) Log.v(TAG, "Learning done"); |
| if (mLearningDoneListener != null) { |
| mLearningDoneListener.onLearningDone(this); |
| } |
| } |
| } |
| } else { |
| Frame output = context.getFrameManager().newFrame(video.getFormat()); |
| Frame[] subtractInputs = { video, background, mMask, mAutoWB }; |
| mBgSubtractProgram.process(subtractInputs, output); |
| pushOutput("video", output); |
| output.release(); |
| } |
| |
| // Compute mean and variance of the background |
| if (mFrameCount < mLearningDuration - mLearningVerifyDuration || |
| mAdaptRateBg > 0.0 || mAdaptRateFg > 0.0) { |
| Frame[] meanUpdateInputs = { mVideoInput, mBgMean[inputIndex], mMask }; |
| mBgUpdateMeanProgram.process(meanUpdateInputs, mBgMean[outputIndex]); |
| mBgMean[outputIndex].generateMipMap(); |
| mBgMean[outputIndex].setTextureParameter(GLES20.GL_TEXTURE_MIN_FILTER, |
| GLES20.GL_LINEAR_MIPMAP_NEAREST); |
| |
| Frame[] varianceUpdateInputs = { |
| mVideoInput, mBgMean[inputIndex], mBgVariance[inputIndex], mMask |
| }; |
| mBgUpdateVarianceProgram.process(varianceUpdateInputs, mBgVariance[outputIndex]); |
| mBgVariance[outputIndex].generateMipMap(); |
| mBgVariance[outputIndex].setTextureParameter(GLES20.GL_TEXTURE_MIN_FILTER, |
| GLES20.GL_LINEAR_MIPMAP_NEAREST); |
| } |
| |
| // Provide debug output to two smaller viewers |
| if (mProvideDebugOutputs) { |
| Frame dbg1 = context.getFrameManager().newFrame(video.getFormat()); |
| mCopyOutProgram.process(video, dbg1); |
| pushOutput("debug1", dbg1); |
| dbg1.release(); |
| |
| Frame dbg2 = context.getFrameManager().newFrame(mMemoryFormat); |
| mCopyOutProgram.process(mMask, dbg2); |
| pushOutput("debug2", dbg2); |
| dbg2.release(); |
| } |
| |
| mFrameCount++; |
| |
| if (mLogVerbose) { |
| if (mFrameCount % 30 == 0) { |
| if (startTime == -1) { |
| context.getGLEnvironment().activate(); |
| GLES20.glFinish(); |
| startTime = SystemClock.elapsedRealtime(); |
| } else { |
| context.getGLEnvironment().activate(); |
| GLES20.glFinish(); |
| long endTime = SystemClock.elapsedRealtime(); |
| Log.v(TAG, "Avg. frame duration: " + String.format("%.2f",(endTime-startTime)/30.) + |
| " ms. Avg. fps: " + String.format("%.2f", 1000./((endTime-startTime)/30.)) ); |
| startTime = endTime; |
| } |
| } |
| } |
| } |
| |
| private long startTime = -1; |
| |
| public void close(FilterContext context) { |
| if (mMemoryFormat == null) { |
| return; |
| } |
| |
| if (mLogVerbose) Log.v(TAG, "Filter Closing!"); |
| for (int i = 0; i < 2; i++) { |
| mBgMean[i].release(); |
| mBgVariance[i].release(); |
| mMaskVerify[i].release(); |
| } |
| mDistance.release(); |
| mMask.release(); |
| mAutoWB.release(); |
| mVideoInput.release(); |
| mBgInput.release(); |
| mMaskAverage.release(); |
| |
| mMemoryFormat = null; |
| } |
| |
| // Relearn background model |
| synchronized public void relearn() { |
| // Let the processing thread know about learning restart |
| mStartLearning = true; |
| } |
| |
| @Override |
| public void fieldPortValueUpdated(String name, FilterContext context) { |
| // TODO: Many of these can be made ProgramPorts! |
| if (name.equals("backgroundFitMode")) { |
| mBackgroundFitModeChanged = true; |
| } else if (name.equals("acceptStddev")) { |
| mBgMaskProgram.setHostValue("accept_variance", mAcceptStddev * mAcceptStddev); |
| } else if (name.equals("hierLrgScale")) { |
| mBgMaskProgram.setHostValue("scale_lrg", mHierarchyLrgScale); |
| } else if (name.equals("hierMidScale")) { |
| mBgMaskProgram.setHostValue("scale_mid", mHierarchyMidScale); |
| } else if (name.equals("hierSmlScale")) { |
| mBgMaskProgram.setHostValue("scale_sml", mHierarchySmlScale); |
| } else if (name.equals("hierLrgExp")) { |
| mBgMaskProgram.setHostValue("exp_lrg", (float)(mSubsampleLevel + mHierarchyLrgExp)); |
| } else if (name.equals("hierMidExp")) { |
| mBgMaskProgram.setHostValue("exp_mid", (float)(mSubsampleLevel + mHierarchyMidExp)); |
| } else if (name.equals("hierSmlExp")) { |
| mBgMaskProgram.setHostValue("exp_sml", (float)(mSubsampleLevel + mHierarchySmlExp)); |
| } else if (name.equals("lumScale") || name.equals("chromaScale")) { |
| float[] yuvWeights = { mLumScale, mChromaScale }; |
| mBgMaskProgram.setHostValue("yuv_weights", yuvWeights ); |
| } else if (name.equals("maskBg")) { |
| mBgSubtractProgram.setHostValue("mask_blend_bg", mMaskBg); |
| } else if (name.equals("maskFg")) { |
| mBgSubtractProgram.setHostValue("mask_blend_fg", mMaskFg); |
| } else if (name.equals("exposureChange")) { |
| mBgSubtractProgram.setHostValue("exposure_change", mExposureChange); |
| } else if (name.equals("whitebalanceredChange")) { |
| mBgSubtractProgram.setHostValue("whitebalancered_change", mWhiteBalanceRedChange); |
| } else if (name.equals("whitebalanceblueChange")) { |
| mBgSubtractProgram.setHostValue("whitebalanceblue_change", mWhiteBalanceBlueChange); |
| } else if (name.equals("autowbToggle")){ |
| mAutomaticWhiteBalanceProgram.setHostValue("autowb_toggle", mAutoWBToggle); |
| } |
| } |
| |
| private void updateBgScaling(Frame video, Frame background, boolean fitModeChanged) { |
| float foregroundAspect = (float)video.getFormat().getWidth() / video.getFormat().getHeight(); |
| float backgroundAspect = (float)background.getFormat().getWidth() / background.getFormat().getHeight(); |
| float currentRelativeAspect = foregroundAspect/backgroundAspect; |
| if (currentRelativeAspect != mRelativeAspect || fitModeChanged) { |
| mRelativeAspect = currentRelativeAspect; |
| float xMin = 0.f, xWidth = 1.f, yMin = 0.f, yWidth = 1.f; |
| switch (mBackgroundFitMode) { |
| case BACKGROUND_STRETCH: |
| // Just map 1:1 |
| break; |
| case BACKGROUND_FIT: |
| if (mRelativeAspect > 1.0f) { |
| // Foreground is wider than background, scale down |
| // background in X |
| xMin = 0.5f - 0.5f * mRelativeAspect; |
| xWidth = 1.f * mRelativeAspect; |
| } else { |
| // Foreground is taller than background, scale down |
| // background in Y |
| yMin = 0.5f - 0.5f / mRelativeAspect; |
| yWidth = 1 / mRelativeAspect; |
| } |
| break; |
| case BACKGROUND_FILL_CROP: |
| if (mRelativeAspect > 1.0f) { |
| // Foreground is wider than background, crop |
| // background in Y |
| yMin = 0.5f - 0.5f / mRelativeAspect; |
| yWidth = 1.f / mRelativeAspect; |
| } else { |
| // Foreground is taller than background, crop |
| // background in X |
| xMin = 0.5f - 0.5f * mRelativeAspect; |
| xWidth = mRelativeAspect; |
| } |
| break; |
| } |
| // If mirroring is required (for ex. the camera mirrors the preview |
| // in the front camera) |
| // TODO: Backdropper does not attempt to apply any other transformation |
| // than just flipping. However, in the current state, it's "x-axis" is always aligned |
| // with the Camera's width. Hence, we need to define the mirroring based on the camera |
| // orientation. In the future, a cleaner design would be to cast away all the rotation |
| // in a separate place. |
| if (mMirrorBg) { |
| if (mLogVerbose) Log.v(TAG, "Mirroring the background!"); |
| // Mirroring in portrait |
| if (mOrientation == 0 || mOrientation == 180) { |
| xWidth = -xWidth; |
| xMin = 1.0f - xMin; |
| } else { |
| // Mirroring in landscape |
| yWidth = -yWidth; |
| yMin = 1.0f - yMin; |
| } |
| } |
| if (mLogVerbose) Log.v(TAG, "bgTransform: xMin, yMin, xWidth, yWidth : " + |
| xMin + ", " + yMin + ", " + xWidth + ", " + yWidth + |
| ", mRelAspRatio = " + mRelativeAspect); |
| // The following matrix is the transpose of the actual matrix |
| float[] bgTransform = {xWidth, 0.f, 0.f, |
| 0.f, yWidth, 0.f, |
| xMin, yMin, 1.f}; |
| mBgSubtractProgram.setHostValue("bg_fit_transform", bgTransform); |
| } |
| } |
| |
| private int pyramidLevel(int size) { |
| return (int)Math.floor(Math.log10(size) / Math.log10(2)) - 1; |
| } |
| |
| } |