Home | History | Annotate | Download | only in simplecamera
      1 /*
      2  * Copyright 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 // Takes sharpness score, rates the image good if above 10, bad otherwise
     17 
     18 package androidx.media.filterfw.samples.simplecamera;
     19 
     20 import android.graphics.Bitmap;
     21 import android.os.AsyncTask;
     22 import android.util.Log;
     23 import android.widget.ImageView;
     24 import androidx.media.filterfw.Filter;
     25 import androidx.media.filterfw.FrameImage2D;
     26 import androidx.media.filterfw.FrameType;
     27 import androidx.media.filterfw.FrameValue;
     28 import androidx.media.filterfw.MffContext;
     29 import androidx.media.filterfw.OutputPort;
     30 import androidx.media.filterfw.Signature;
     31 
     32 public class ImageGoodnessFilter extends Filter {
     33 
     34     private static final String TAG = "ImageGoodnessFilter";
     35     private static boolean mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
     36 
     37     private final static String GREAT = "Great Picture!";
     38     private final static String GOOD = "Good Picture!";
     39     private final static String OK = "Ok Picture";
     40     private final static String BAD = "Bad Picture";
     41     private final static String AWFUL = "Awful Picture";
     42     private final static float SMALL_SCORE_INC = 0.25f;
     43     private final static float BIG_SCORE_INC = 0.5f;
     44     private final static float LOW_VARIANCE = 0.1f;
     45     private final static float MEDIUM_VARIANCE = 10;
     46     private final static float HIGH_VARIANCE = 100;
     47     private float sharpnessMean = 0;
     48     private float sharpnessVar = 0;
     49     private float underExposureMean = 0;
     50     private float underExposureVar = 0;
     51     private float overExposureMean = 0;
     52     private float overExposureVar = 0;
     53     private float contrastMean = 0;
     54     private float contrastVar = 0;
     55     private float colorfulnessMean = 0;
     56     private float colorfulnessVar = 0;
     57     private float brightnessMean = 0;
     58     private float brightnessVar = 0;
     59 
     60     private float motionMean = 0;
     61     private float scoreMean = 0;
     62     private static final float DECAY = 0.03f;
     63     /**
     64      * @param context
     65      * @param name
     66      */
     67     public ImageGoodnessFilter(MffContext context, String name) {
     68         super(context, name);
     69     }
     70 
     71     @Override
     72     public Signature getSignature() {
     73         FrameType floatT = FrameType.single(float.class);
     74         FrameType imageIn = FrameType.image2D(FrameType.ELEMENT_RGBA8888, FrameType.READ_GPU);
     75 
     76         return new Signature()
     77                 .addInputPort("sharpness", Signature.PORT_REQUIRED, floatT)
     78                 .addInputPort("overExposure", Signature.PORT_REQUIRED, floatT)
     79                 .addInputPort("underExposure", Signature.PORT_REQUIRED, floatT)
     80                 .addInputPort("colorfulness", Signature.PORT_REQUIRED, floatT)
     81                 .addInputPort("contrastRating", Signature.PORT_REQUIRED, floatT)
     82                 .addInputPort("motionValues", Signature.PORT_REQUIRED, FrameType.array(float.class))
     83                 .addInputPort("brightness", Signature.PORT_REQUIRED, floatT)
     84                 .addInputPort("capturing", Signature.PORT_REQUIRED, FrameType.single(boolean.class))
     85                 .addInputPort("image", Signature.PORT_REQUIRED, imageIn)
     86                 .addOutputPort("goodOrBadPic", Signature.PORT_REQUIRED,
     87                         FrameType.single(String.class))
     88                 .addOutputPort("score", Signature.PORT_OPTIONAL, floatT)
     89                 .disallowOtherPorts();
     90     }
     91 
     92     /**
     93      * @see androidx.media.filterfw.Filter#onProcess()
     94      */
     95     @Override
     96     protected void onProcess() {
     97         FrameValue sharpnessFrameValue =
     98                 getConnectedInputPort("sharpness").pullFrame().asFrameValue();
     99         float sharpness = ((Float)sharpnessFrameValue.getValue()).floatValue();
    100 
    101         FrameValue overExposureFrameValue =
    102                 getConnectedInputPort("overExposure").pullFrame().asFrameValue();
    103         float overExposure = ((Float)overExposureFrameValue.getValue()).floatValue();
    104 
    105         FrameValue underExposureFrameValue =
    106                 getConnectedInputPort("underExposure").pullFrame().asFrameValue();
    107         float underExposure = ((Float)underExposureFrameValue.getValue()).floatValue();
    108 
    109         FrameValue colorfulnessFrameValue =
    110                 getConnectedInputPort("colorfulness").pullFrame().asFrameValue();
    111         float colorfulness = ((Float)colorfulnessFrameValue.getValue()).floatValue();
    112 
    113         FrameValue contrastRatingFrameValue =
    114                 getConnectedInputPort("contrastRating").pullFrame().asFrameValue();
    115         float contrastRating = ((Float)contrastRatingFrameValue.getValue()).floatValue();
    116 
    117         FrameValue brightnessFrameValue =
    118                 getConnectedInputPort("brightness").pullFrame().asFrameValue();
    119         float brightness = ((Float)brightnessFrameValue.getValue()).floatValue();
    120 
    121         FrameValue motionValuesFrameValue =
    122                 getConnectedInputPort("motionValues").pullFrame().asFrameValue();
    123         float[] motionValues = (float[]) motionValuesFrameValue.getValue();
    124 
    125 
    126         float vectorAccel = (float) Math.sqrt(Math.pow(motionValues[0], 2) +
    127                 Math.pow(motionValues[1], 2) + Math.pow(motionValues[2], 2));
    128         String outStr;
    129 
    130         FrameValue capturingFrameValue =
    131                 getConnectedInputPort("capturing").pullFrame().asFrameValue();
    132         boolean capturing = (Boolean) capturingFrameValue.getValue();
    133 
    134         FrameImage2D inputImage = getConnectedInputPort("image").pullFrame().asFrameImage2D();
    135 
    136 
    137         // TODO: get rid of magic numbers
    138         float score = 0.0f;
    139         score = computePictureScore(vectorAccel, sharpness, underExposure, overExposure,
    140                     contrastRating, colorfulness, brightness);
    141         if (scoreMean == 0) scoreMean = score;
    142         else scoreMean = scoreMean * (1 - DECAY) + score * DECAY;
    143 
    144         if (motionMean == 0) motionMean = vectorAccel;
    145         else motionMean = motionMean * (1 - DECAY) + vectorAccel * DECAY;
    146 
    147         float classifierScore = classifierComputeScore(vectorAccel, sharpness, underExposure,
    148                 colorfulness, contrastRating, score);
    149 
    150 //        Log.v(TAG, "ClassifierScore:: " + classifierScore);
    151         final float GREAT_SCORE = 3.5f;
    152         final float GOOD_SCORE = 2.5f;
    153         final float OK_SCORE = 1.5f;
    154         final float BAD_SCORE = 0.5f;
    155 
    156         if (score >= GREAT_SCORE) {
    157             outStr = GREAT;
    158         } else if (score >= GOOD_SCORE) {
    159             outStr = GOOD;
    160         } else if (score >= OK_SCORE) {
    161             outStr = OK;
    162         } else if (score >= BAD_SCORE) {
    163             outStr = BAD;
    164         } else {
    165             outStr = AWFUL;
    166         }
    167 
    168         if(capturing) {
    169             if (outStr.equals(GREAT)) {
    170                 // take a picture
    171                 Bitmap bitmap = inputImage.toBitmap();
    172 
    173                 new AsyncOperation().execute(bitmap);
    174                 final float RESET_FEATURES = 0.01f;
    175                 sharpnessMean = RESET_FEATURES;
    176                 underExposureMean = RESET_FEATURES;
    177                 overExposureMean = RESET_FEATURES;
    178                 contrastMean = RESET_FEATURES;
    179                 colorfulnessMean = RESET_FEATURES;
    180                 brightnessMean = RESET_FEATURES;
    181             }
    182         }
    183 
    184         OutputPort outPort = getConnectedOutputPort("goodOrBadPic");
    185         FrameValue stringFrame = outPort.fetchAvailableFrame(null).asFrameValue();
    186         stringFrame.setValue(outStr);
    187         outPort.pushFrame(stringFrame);
    188 
    189         OutputPort scoreOutPort = getConnectedOutputPort("score");
    190         FrameValue scoreFrame = scoreOutPort.fetchAvailableFrame(null).asFrameValue();
    191         scoreFrame.setValue(score);
    192         scoreOutPort.pushFrame(scoreFrame);
    193 
    194     }
    195 
    196     private class AsyncOperation extends AsyncTask<Bitmap, Void, String> {
    197         private Bitmap b;
    198         protected void onPostExecute(String result) {
    199             ImageView view = SmartCamera.getImageView();
    200             view.setImageBitmap(b);
    201         }
    202 
    203         @Override
    204         protected String doInBackground(Bitmap... params) {
    205             // TODO Auto-generated method stub
    206             b = params[0];
    207             return null;
    208         }
    209 
    210     }
    211     // Returns a number between -1 and 1
    212     private float classifierComputeScore(float vectorAccel, float sharpness, float underExposure,
    213            float colorfulness, float contrast, float score) {
    214         float result = (-0.0223f * sharpness + -0.0563f * underExposure + 0.0137f * colorfulness
    215                 + 0.3102f * contrast + 0.0314f * vectorAccel + -0.0094f * score + 0.0227f *
    216                 sharpnessMean + 0.0459f * underExposureMean + -0.3934f * contrastMean +
    217                 -0.0697f * motionMean + 0.0091f * scoreMean + -0.0152f);
    218         return result;
    219     }
    220 
    221     // Returns a number between -1 and 4 representing the score for this picture
    222     private float computePictureScore(float vector_accel, float sharpness,
    223             float underExposure, float overExposure, float contrastRating, float colorfulness,
    224             float brightness) {
    225         final float ACCELERATION_THRESHOLD_VERY_STEADY = 0.1f;
    226         final float ACCELERATION_THRESHOLD_STEADY = 0.3f;
    227         final float ACCELERATION_THRESHOLD_MOTION = 2f;
    228 
    229         float score = 0.0f;
    230         if (vector_accel > ACCELERATION_THRESHOLD_MOTION) {
    231             score -= (BIG_SCORE_INC + BIG_SCORE_INC); // set score to -1, bad pic
    232         } else if (vector_accel > ACCELERATION_THRESHOLD_STEADY) {
    233             score -= BIG_SCORE_INC;
    234             score = subComputeScore(sharpness, underExposure, overExposure, contrastRating,
    235                     colorfulness, brightness, score);
    236         } else if (vector_accel < ACCELERATION_THRESHOLD_VERY_STEADY) {
    237             score += BIG_SCORE_INC;
    238             score = subComputeScore(sharpness, underExposure, overExposure, contrastRating,
    239                     colorfulness, brightness, score);
    240         } else {
    241             score = subComputeScore(sharpness, underExposure, overExposure, contrastRating,
    242                     colorfulness, brightness, score);
    243         }
    244         return score;
    245     }
    246 
    247     // Changes the score by at most +/- 3.5
    248     private float subComputeScore(float sharpness, float underExposure, float overExposure,
    249                 float contrastRating, float colorfulness, float brightness, float score) {
    250         // The score methods return values -0.5 to 0.5
    251         final float SHARPNESS_WEIGHT = 2;
    252         score += SHARPNESS_WEIGHT * sharpnessScore(sharpness);
    253         score += underExposureScore(underExposure);
    254         score += overExposureScore(overExposure);
    255         score += contrastScore(contrastRating);
    256         score += colorfulnessScore(colorfulness);
    257         score += brightnessScore(brightness);
    258         return score;
    259     }
    260 
    261     private float sharpnessScore(float sharpness) {
    262         if (sharpnessMean == 0) {
    263             sharpnessMean = sharpness;
    264             sharpnessVar = 0;
    265             return 0;
    266         } else {
    267             sharpnessMean = sharpnessMean * (1 - DECAY) + sharpness * DECAY;
    268             sharpnessVar = sharpnessVar * (1 - DECAY) + (sharpness - sharpnessMean) *
    269                     (sharpness - sharpnessMean) * DECAY;
    270             if (sharpnessVar < LOW_VARIANCE) {
    271                 return BIG_SCORE_INC;
    272             } else if (sharpness < sharpnessMean && sharpnessVar > MEDIUM_VARIANCE) {
    273                 return -BIG_SCORE_INC;
    274             } else if (sharpness < sharpnessMean) {
    275                 return -SMALL_SCORE_INC;
    276             } else if (sharpness > sharpnessMean && sharpnessVar > HIGH_VARIANCE) {
    277                 return 0;
    278             } else if (sharpness > sharpnessMean && sharpnessVar > MEDIUM_VARIANCE) {
    279                 return SMALL_SCORE_INC;
    280             } else  {
    281                 return BIG_SCORE_INC; // low variance, sharpness above the mean
    282             }
    283         }
    284     }
    285 
    286     private float underExposureScore(float underExposure) {
    287         if (underExposureMean == 0) {
    288             underExposureMean = underExposure;
    289             underExposureVar = 0;
    290             return 0;
    291         } else {
    292             underExposureMean = underExposureMean * (1 - DECAY) + underExposure * DECAY;
    293             underExposureVar = underExposureVar * (1 - DECAY) + (underExposure - underExposureMean)
    294                     * (underExposure - underExposureMean) * DECAY;
    295             if (underExposureVar < LOW_VARIANCE) {
    296                 return BIG_SCORE_INC;
    297             } else if (underExposure > underExposureMean && underExposureVar > MEDIUM_VARIANCE) {
    298                 return -BIG_SCORE_INC;
    299             } else if (underExposure > underExposureMean) {
    300                 return -SMALL_SCORE_INC;
    301             } else if (underExposure < underExposureMean && underExposureVar > HIGH_VARIANCE) {
    302                 return 0;
    303             } else if (underExposure < underExposureMean && underExposureVar > MEDIUM_VARIANCE) {
    304                 return SMALL_SCORE_INC;
    305             } else {
    306                 return BIG_SCORE_INC; // low variance, underExposure below the mean
    307             }
    308         }
    309     }
    310 
    311     private float overExposureScore(float overExposure) {
    312         if (overExposureMean == 0) {
    313             overExposureMean = overExposure;
    314             overExposureVar = 0;
    315             return 0;
    316         } else {
    317             overExposureMean = overExposureMean * (1 - DECAY) + overExposure * DECAY;
    318             overExposureVar = overExposureVar * (1 - DECAY) + (overExposure - overExposureMean) *
    319                     (overExposure - overExposureMean) * DECAY;
    320             if (overExposureVar < LOW_VARIANCE) {
    321                 return BIG_SCORE_INC;
    322             } else if (overExposure > overExposureMean && overExposureVar > MEDIUM_VARIANCE) {
    323                 return -BIG_SCORE_INC;
    324             } else if (overExposure > overExposureMean) {
    325                 return -SMALL_SCORE_INC;
    326             } else if (overExposure < overExposureMean && overExposureVar > HIGH_VARIANCE) {
    327                 return 0;
    328             } else if (overExposure < overExposureMean && overExposureVar > MEDIUM_VARIANCE) {
    329                 return SMALL_SCORE_INC;
    330             } else {
    331                 return BIG_SCORE_INC; // low variance, overExposure below the mean
    332             }
    333         }
    334     }
    335 
    336     private float contrastScore(float contrast) {
    337         if (contrastMean == 0) {
    338             contrastMean = contrast;
    339             contrastVar = 0;
    340             return 0;
    341         } else {
    342             contrastMean = contrastMean * (1 - DECAY) + contrast * DECAY;
    343             contrastVar = contrastVar * (1 - DECAY) + (contrast - contrastMean) *
    344                     (contrast - contrastMean) * DECAY;
    345             if (contrastVar < LOW_VARIANCE) {
    346                 return BIG_SCORE_INC;
    347             } else if (contrast < contrastMean && contrastVar > MEDIUM_VARIANCE) {
    348                 return -BIG_SCORE_INC;
    349             } else if (contrast < contrastMean) {
    350                 return -SMALL_SCORE_INC;
    351             } else if (contrast > contrastMean && contrastVar > 100) {
    352                 return 0;
    353             } else if (contrast > contrastMean && contrastVar > MEDIUM_VARIANCE) {
    354                 return SMALL_SCORE_INC;
    355             } else {
    356                 return BIG_SCORE_INC; // low variance, contrast above the mean
    357             }
    358         }
    359     }
    360 
    361     private float colorfulnessScore(float colorfulness) {
    362         if (colorfulnessMean == 0) {
    363             colorfulnessMean = colorfulness;
    364             colorfulnessVar = 0;
    365             return 0;
    366         } else {
    367             colorfulnessMean = colorfulnessMean * (1 - DECAY) + colorfulness * DECAY;
    368             colorfulnessVar = colorfulnessVar * (1 - DECAY) + (colorfulness - colorfulnessMean) *
    369                     (colorfulness - colorfulnessMean) * DECAY;
    370             if (colorfulnessVar < LOW_VARIANCE) {
    371                 return BIG_SCORE_INC;
    372             } else if (colorfulness < colorfulnessMean && colorfulnessVar > MEDIUM_VARIANCE) {
    373                 return -BIG_SCORE_INC;
    374             } else if (colorfulness < colorfulnessMean) {
    375                 return -SMALL_SCORE_INC;
    376             } else if (colorfulness > colorfulnessMean && colorfulnessVar > 100) {
    377                 return 0;
    378             } else if (colorfulness > colorfulnessMean && colorfulnessVar > MEDIUM_VARIANCE) {
    379                 return SMALL_SCORE_INC;
    380             } else {
    381                 return BIG_SCORE_INC; // low variance, colorfulness above the mean
    382             }
    383         }
    384     }
    385 
    386     private float brightnessScore(float brightness) {
    387         if (brightnessMean == 0) {
    388             brightnessMean = brightness;
    389             brightnessVar = 0;
    390             return 0;
    391         } else {
    392             brightnessMean = brightnessMean * (1 - DECAY) + brightness * DECAY;
    393             brightnessVar = brightnessVar * (1 - DECAY) + (brightness - brightnessMean) *
    394                     (brightness - brightnessMean) * DECAY;
    395             if (brightnessVar < LOW_VARIANCE) {
    396                 return BIG_SCORE_INC;
    397             } else if (brightness < brightnessMean && brightnessVar > MEDIUM_VARIANCE) {
    398                 return -BIG_SCORE_INC;
    399             } else if (brightness < brightnessMean) {
    400                 return -SMALL_SCORE_INC;
    401             } else if (brightness > brightnessMean && brightnessVar > 100) {
    402                 return 0;
    403             } else if (brightness > brightnessMean && brightnessVar > MEDIUM_VARIANCE) {
    404                 return SMALL_SCORE_INC;
    405             } else {
    406                 return BIG_SCORE_INC; // low variance, brightness above the mean
    407             }
    408         }
    409     }
    410 }
    411