Search code examples
javaimagefileazure-cognitive-servicesphoto

Pixelate the faces that come to me from an image and resize pixel size


I am trying to pixelate the faces that come to me from an image. I am using the Azure API to pixelate the faces, and it returns me where the faces are. The problem is that depending on the size of the face, one pixelation or another must be applied. For example, for a 20x20 face, it is not worth applying a pixel of size 10. But it is worth applying a pixel of 10 on a 100x100 face, for example.

thanks in advance!

/**
 * Pixela las caras en una imagen.
 * Lee una imagen desde un archivo, y para cada cara detectada en la imagen (faces) ,
 * recorre cada bloque de pixels del tamaño especificado en la región de la cara. Cada bloque de
 * pixels se llena con el color del pixel superior izquierdo del bloque.
 * Finalmente, la imagen modificada se vuelve a escribir en el archivo original.
 *
 * @param pixelSize Tamaño de los pixels en la imagen pixelada.
 * @param inputFile Archivo de la imagen a pixelar.
 * @param faces     Lista de caras detectadas en la imagen.
 *
 * @throws IOException Si ocurre un error durante la lectura o la escritura del archivo de la imagen.
 */
public static PixelatedOutput pixelateFaces(int pixelSize, File inputFile,
                                            List<AzureComputerVisionService.FaceDetectionResponse> faces)
throws IOException {
    BufferedImage bufferedImage = ImageIO.read(inputFile);
    File f = File.createTempFile("pixelate", ".jpg");

    // Iterar sobre cada cara detectada
    for (AzureComputerVisionService.FaceDetectionResponse face : faces) {
        AzureComputerVisionService.FaceDetectionResponse.FaceRectangle faceRectangle =
                face.faceRectangle;
        //por cada cara nuevo pixel size.
        pixelSize = calculatePixelSize(faceRectangle, pixelSize);
        int rectangleRight = faceRectangle.left + faceRectangle.width;
        int rectangleBottom = faceRectangle.top + faceRectangle.height;

        for (int y = faceRectangle.top; y < rectangleBottom; y += pixelSize) {
            for (int x = faceRectangle.left; x < rectangleRight; x += pixelSize) {

                int pixelColor = bufferedImage.getRGB(x, y);

                int pixelBlockWidth = Math.min(x + pixelSize, rectangleRight);
                int pixelBlockHeight = Math.min(y + pixelSize, rectangleBottom);

                for (int yd = y; yd < pixelBlockHeight; yd++) {
                    for (int xd = x; xd < pixelBlockWidth; xd++) {
                        bufferedImage.setRGB(xd, yd, pixelColor);
                    }
                }
            }
        }
    }
    ImageIO.write(bufferedImage, "jpg", f);
    PixelatedOutput pixelatedOutput = new PixelatedOutput();
    pixelatedOutput.setFile(f);
    pixelatedOutput.setContentType("image/jpeg");
    return pixelatedOutput;
}

/**
 * Calcula el tamaño del píxel para una cara dada, basándose en la dimensión mínima de la cara (ancho o alto, lo que sea menor)
 * y el número máximo de bloques de píxeles que representarán la cara, limitado a 36.
 * <p>
 * El método funciona tomando la dimensión mínima de la cara, la cual siempre será mayor o igual a 36 píxeles, y dividiéndola
 * por el número máximo de bloques de píxeles (limitado a un rango de 2 a 36). Esto calcula el tamaño de cada píxel, asegurando
 * que la representación de la cara tendrá exactamente {@code maxPixelBlocks} píxeles, y cada píxel tendrá un tamaño proporcional
 * al tamaño de la cara.
 * <p>
 * Esto permite representar la cara con un nivel de detalle constante, independientemente de su tamaño.
 * <p>
 * Para más información sobre el tamaño mínimo detectable de la cara, consultear el service:
 * <a href="https://westus.dev.cognitive.microsoft.com/docs/services/563879b61984550e40cbbe8d/operations/563879b61984550f30395236">documentación oficial de Azure</a>.
 *
 * @param faceRectangle Rectángulo que define las dimensiones de la cara. Las dimensiones deben ser mayores que cero y mínimo de 36 píxeles. (asi lo dice azure).
 * @param maxPixelBlocks Número máximo de bloques de píxeles permitidos para representar la cara, debe estar en el rango de 2 a 36.
 * @return El tamaño del píxel calculado, que es la dimensión mínima de la cara dividida por {@code maxPixelBlocks}, con un mínimo de 2.
 * @throws ArithmeticException si {@code maxPixelBlocks} es menor o igual a 0 o mayor que 36.
 */
public static int calculatePixelSize(AzureComputerVisionService.FaceDetectionResponse.FaceRectangle faceRectangle,
                                      int maxPixelBlocks) {
    // Escogemos la mínima dimensión entre el ancho y el alto. Nos determina siempre el MIN.
    if(maxPixelBlocks<=1 || maxPixelBlocks>36){
        log.error("Número máximo de bloques de píxeles permitidos para representar la cara no puede ser 0 o menor o mayor que 36");
        throw new ArithmeticException();
    }
    int minDimension = Math.min(faceRectangle.width, faceRectangle.height);
    int pixelSize = minDimension / maxPixelBlocks; //no va a ser
    // Asegurarse de que el tamaño del píxel sea al menos un valor mínimo. (en el caso que sea 1).
    pixelSize = Math.max(pixelSize, 10);
    return pixelSize;
}

Solution

  • Calculate the scaling factor as a ratio of the face size to a reference size (e.g., 100x100). Then, you can multiply this scaling factor with your fixed minimum pixel size (e.g., 10) to get a dynamically adjusted minimum pixel size.

    double scalingFactor = (double) minDimension / referenceSize;
    int dynamicMinPixelSize = (int) (scalingFactor * 10); // Adjust 10 based on your needs
    int pixelSize = Math.max(pixelSize, dynamicMinPixelSize);
    
    • Instead of iterating over each pixel within the block, you can use the BufferedImage method setRGB(int startX, int startY, int w, int h, int[] rgbArray, int offset, int scansize) to set a block of pixels in one operation.
    int[] pixelArray = new int[pixelBlockWidth - x];
    Arrays.fill(pixelArray, pixelColor);
    
    for (int yd = y; yd < pixelBlockHeight; yd++) {
        bufferedImage.setRGB(x, yd, pixelBlockWidth - x, 1, pixelArray, 0, pixelBlockWidth - x);
    }
    
    • Throwing an ArithmeticException if the maxPixelBlocks value is not within the valid range. It might be better to throw an IllegalArgumentException instead.
    if (maxPixelBlocks <= 1 || maxPixelBlocks > 36) {
        throw new IllegalArgumentException("Number of pixel blocks must be in the range of 2 to 36");
    }
    

    I am able get the data that which I send the image.

    Result: enter image description here

    • Simple block filling might not produce the best quality output. There are algorithms that average the colors within each block, which can lead to smoother pixelation effects.