mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 20:33:53 +01:00
Improve the algorithm for detecting unwanted images. (#1798)
* improve the algorithm for checking if the image is dark. ImageUtils.java: algorithm used previously gave the 'TOO DARK' message for images that were perfectly valid. The modified algorithm solves this problem. * Clean up the code
This commit is contained in:
parent
92a8b5e37c
commit
13c377aa17
1 changed files with 39 additions and 59 deletions
|
|
@ -8,6 +8,7 @@ import android.graphics.Color;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import com.facebook.common.executors.CallerThreadExecutor;
|
import com.facebook.common.executors.CallerThreadExecutor;
|
||||||
import com.facebook.common.references.CloseableReference;
|
import com.facebook.common.references.CloseableReference;
|
||||||
|
|
@ -29,11 +30,6 @@ import timber.log.Timber;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class ImageUtils {
|
public class ImageUtils {
|
||||||
//atleast 50% of the image in question should be considered dark for the entire image to be dark
|
|
||||||
private static final double MINIMUM_DARKNESS_FACTOR = 0.50;
|
|
||||||
//atleast 50% of the image in question should be considered blurry for the entire image to be blurry
|
|
||||||
private static final double MINIMUM_BLURRYNESS_FACTOR = 0.50;
|
|
||||||
private static final int LAPLACIAN_VARIANCE_THRESHOLD = 70;
|
|
||||||
|
|
||||||
public enum Result {
|
public enum Result {
|
||||||
IMAGE_DARK,
|
IMAGE_DARK,
|
||||||
|
|
@ -41,13 +37,6 @@ public class ImageUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BitmapRegionDecoder allows us to process a large bitmap by breaking it down into smaller rectangles. The rectangles
|
|
||||||
* are obtained by setting an initial width, height and start position of the rectangle as a factor of the width and
|
|
||||||
* height of the original bitmap and then manipulating the width, height and position to loop over the entire original
|
|
||||||
* bitmap. Each individual rectangle is independently processed to check if its too dark. Based on
|
|
||||||
* the factor of "bright enough" individual rectangles amongst the total rectangles into which the image
|
|
||||||
* was divided, we will declare the image as wanted/unwanted
|
|
||||||
*
|
|
||||||
* @param bitmapRegionDecoder BitmapRegionDecoder for the image we wish to process
|
* @param bitmapRegionDecoder BitmapRegionDecoder for the image we wish to process
|
||||||
* @return Result.IMAGE_OK if image is neither dark nor blurry or if the input bitmapRegionDecoder provided is null
|
* @return Result.IMAGE_OK if image is neither dark nor blurry or if the input bitmapRegionDecoder provided is null
|
||||||
* Result.IMAGE_DARK if image is too dark
|
* Result.IMAGE_DARK if image is too dark
|
||||||
|
|
@ -62,39 +51,15 @@ public class ImageUtils {
|
||||||
int loadImageWidth = bitmapRegionDecoder.getWidth();
|
int loadImageWidth = bitmapRegionDecoder.getWidth();
|
||||||
|
|
||||||
int checkImageTopPosition = 0;
|
int checkImageTopPosition = 0;
|
||||||
int checkImageBottomPosition = loadImageHeight / 10;
|
|
||||||
int checkImageLeftPosition = 0;
|
int checkImageLeftPosition = 0;
|
||||||
int checkImageRightPosition = loadImageWidth / 10;
|
|
||||||
|
|
||||||
int totalDividedRectangles = 0;
|
Timber.v("left: " + checkImageLeftPosition + " right: " + loadImageWidth + " top: " + checkImageTopPosition + " bottom: " + loadImageHeight);
|
||||||
int numberOfDarkRectangles = 0;
|
|
||||||
|
|
||||||
while ((checkImageRightPosition <= loadImageWidth) && (checkImageLeftPosition < checkImageRightPosition)) {
|
Rect rect = new Rect(checkImageLeftPosition,checkImageTopPosition, loadImageWidth, loadImageHeight);
|
||||||
while ((checkImageBottomPosition <= loadImageHeight) && (checkImageTopPosition < checkImageBottomPosition)) {
|
|
||||||
Timber.v("left: " + checkImageLeftPosition + " right: " + checkImageRightPosition + " top: " + checkImageTopPosition + " bottom: " + checkImageBottomPosition);
|
|
||||||
|
|
||||||
Rect rect = new Rect(checkImageLeftPosition,checkImageTopPosition,checkImageRightPosition,checkImageBottomPosition);
|
|
||||||
totalDividedRectangles++;
|
|
||||||
|
|
||||||
Bitmap processBitmap = bitmapRegionDecoder.decodeRegion(rect,null);
|
Bitmap processBitmap = bitmapRegionDecoder.decodeRegion(rect,null);
|
||||||
|
|
||||||
if (checkIfImageIsDark(processBitmap)) {
|
if (checkIfImageIsDark(processBitmap)) {
|
||||||
numberOfDarkRectangles++;
|
|
||||||
}
|
|
||||||
|
|
||||||
checkImageTopPosition = checkImageBottomPosition;
|
|
||||||
checkImageBottomPosition += (checkImageBottomPosition < (loadImageHeight - checkImageBottomPosition)) ? checkImageBottomPosition : (loadImageHeight - checkImageBottomPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
checkImageTopPosition = 0; //reset to start
|
|
||||||
checkImageBottomPosition = loadImageHeight / 10; //reset to start
|
|
||||||
checkImageLeftPosition = checkImageRightPosition;
|
|
||||||
checkImageRightPosition += (checkImageRightPosition < (loadImageWidth - checkImageRightPosition)) ? checkImageRightPosition : (loadImageWidth - checkImageRightPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
Timber.d("dark rectangles count = " + numberOfDarkRectangles + ", total rectangles count = " + totalDividedRectangles);
|
|
||||||
|
|
||||||
if (numberOfDarkRectangles > totalDividedRectangles * MINIMUM_DARKNESS_FACTOR) {
|
|
||||||
return Result.IMAGE_DARK;
|
return Result.IMAGE_DARK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,14 +69,12 @@ public class ImageUtils {
|
||||||
/**
|
/**
|
||||||
* Pulls the pixels into an array and then runs through it while checking the brightness of each pixel.
|
* Pulls the pixels into an array and then runs through it while checking the brightness of each pixel.
|
||||||
* The calculation of brightness of each pixel is done by extracting the RGB constituents of the pixel
|
* The calculation of brightness of each pixel is done by extracting the RGB constituents of the pixel
|
||||||
* and then applying the formula to calculate its "Luminance". If this brightness value is less than
|
* and then applying the formula to calculate its "Luminance".
|
||||||
* 50 then the pixel is considered to be dark. Based on the MINIMUM_DARKNESS_FACTOR if enough pixels
|
* Pixels with luminance greater than 40% are considered to be bright pixels while the ones with luminance
|
||||||
* are dark then the entire bitmap is considered to be dark.
|
* greater than 26% but less than 40% are considered to be pixels with medium brightness. The rest are
|
||||||
*
|
* dark pixels.
|
||||||
* <p>For more information on this brightness/darkness calculation technique refer the accepted answer
|
* If the number of bright pixels is more than 2.5% or the number of pixels with medium brightness is
|
||||||
* on this -> https://stackoverflow.com/questions/35914461/how-to-detect-dark-photos-in-android/35914745
|
* more than 30% of the total number of pixels then the image is considered to be OK else dark.
|
||||||
* SO question and follow the trail.
|
|
||||||
*
|
|
||||||
* @param bitmap The bitmap that needs to be checked.
|
* @param bitmap The bitmap that needs to be checked.
|
||||||
* @return true if bitmap is dark or null, false if bitmap is bright
|
* @return true if bitmap is dark or null, false if bitmap is bright
|
||||||
*/
|
*/
|
||||||
|
|
@ -126,28 +89,45 @@ public class ImageUtils {
|
||||||
|
|
||||||
int allPixelsCount = bitmapWidth * bitmapHeight;
|
int allPixelsCount = bitmapWidth * bitmapHeight;
|
||||||
int[] bitmapPixels = new int[allPixelsCount];
|
int[] bitmapPixels = new int[allPixelsCount];
|
||||||
|
Log.e("total", Integer.toString(allPixelsCount));
|
||||||
|
|
||||||
bitmap.getPixels(bitmapPixels,0,bitmapWidth,0,0,bitmapWidth,bitmapHeight);
|
bitmap.getPixels(bitmapPixels,0,bitmapWidth,0,0,bitmapWidth,bitmapHeight);
|
||||||
boolean isImageDark = false;
|
int numberOfBrightPixels = 0;
|
||||||
int darkPixelsCount = 0;
|
int numberOfMediumBrightnessPixels = 0;
|
||||||
|
double brightPixelThreshold = 0.025*allPixelsCount;
|
||||||
|
double mediumBrightPixelThreshold = 0.3*allPixelsCount;
|
||||||
|
|
||||||
for (int pixel : bitmapPixels) {
|
for (int pixel : bitmapPixels) {
|
||||||
int r = Color.red(pixel);
|
int r = Color.red(pixel);
|
||||||
int g = Color.green(pixel);
|
int g = Color.green(pixel);
|
||||||
int b = Color.blue(pixel);
|
int b = Color.blue(pixel);
|
||||||
|
|
||||||
int brightness = (int) (0.2126 * r + 0.7152 * g + 0.0722 * b);
|
int secondMax = r>g ? r:g;
|
||||||
if (brightness < 50) {
|
double max = (secondMax>b ? secondMax:b)/255.0;
|
||||||
//pixel is dark
|
|
||||||
darkPixelsCount++;
|
int secondMin = r<g ? r:g;
|
||||||
if (darkPixelsCount > allPixelsCount * MINIMUM_DARKNESS_FACTOR) {
|
double min = (secondMin<b ? secondMin:b)/255.0;
|
||||||
isImageDark = true;
|
|
||||||
break;
|
double luminance = ((max+min)/2.0)*100;
|
||||||
|
|
||||||
|
int highBrightnessLuminance = 40;
|
||||||
|
int mediumBrightnessLuminance = 26;
|
||||||
|
|
||||||
|
if (luminance<highBrightnessLuminance){
|
||||||
|
if (luminance>mediumBrightnessLuminance){
|
||||||
|
numberOfMediumBrightnessPixels++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
numberOfBrightPixels++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return isImageDark;
|
if (numberOfBrightPixels>=brightPixelThreshold || numberOfMediumBrightnessPixels>=mediumBrightPixelThreshold){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue