diff --git a/mobile/lib/utils/image_ml_util.dart b/mobile/lib/utils/image_ml_util.dart index 72be807a51..2610533240 100644 --- a/mobile/lib/utils/image_ml_util.dart +++ b/mobile/lib/utils/image_ml_util.dart @@ -23,15 +23,6 @@ final _logger = Logger("ImageMlUtil"); /// These are 8 bit unsigned integers in range 0-255 for each RGB channel typedef RGB = (int, int, int); -const gaussianKernelSize = 5; -const gaussianKernelRadius = gaussianKernelSize ~/ 2; -const gaussianSigma = 10.0; -final List> gaussianKernel = - create2DGaussianKernel(gaussianKernelSize, gaussianSigma); - -const maxKernelSize = gaussianKernelSize; -const maxKernelRadius = maxKernelSize ~/ 2; - const List supportedRustImageFormats = [ 'bmp', 'dds', @@ -201,45 +192,6 @@ Future> generateFaceThumbnailsUsingCanvas( } } -Future<(Float32List, Dimensions)> preprocessImageYoloFace( - Dimensions image, - Uint8List rawRgbaBytes, -) async { - const requiredWidth = 640; - const requiredHeight = 640; - final scale = min(requiredWidth / image.width, requiredHeight / image.height); - final scaledWidth = (image.width * scale).round().clamp(0, requiredWidth); - final scaledHeight = (image.height * scale).round().clamp(0, requiredHeight); - - final processedBytes = Float32List(3 * requiredHeight * requiredWidth); - - final buffer = Float32List.view(processedBytes.buffer); - int pixelIndex = 0; - const int channelOffsetGreen = requiredHeight * requiredWidth; - const int channelOffsetBlue = 2 * requiredHeight * requiredWidth; - for (var h = 0; h < requiredHeight; h++) { - for (var w = 0; w < requiredWidth; w++) { - late RGB pixel; - if (w >= scaledWidth || h >= scaledHeight) { - pixel = const (114, 114, 114); - } else { - pixel = _getPixelBilinear( - w / scale, - h / scale, - image, - rawRgbaBytes, - ); - } - buffer[pixelIndex] = pixel.$1 / 255; - buffer[pixelIndex + channelOffsetGreen] = pixel.$2 / 255; - buffer[pixelIndex + channelOffsetBlue] = pixel.$3 / 255; - pixelIndex++; - } - } - - return (processedBytes, Dimensions(width: scaledWidth, height: scaledHeight)); -} - Future resizedToPreprocessedYoloFace( Uint8List rgbBytes, int rgbWidth, @@ -309,44 +261,6 @@ Future resizedToPreprocessedClip( return processedBytes; } -Future preprocessImageClip( - Dimensions image, - Uint8List rawRgbaBytes, -) async { - const int requiredWidth = 256; - const int requiredHeight = 256; - const int requiredSize = 3 * requiredWidth * requiredHeight; - final scale = max(requiredWidth / image.width, requiredHeight / image.height); - final bool useAntiAlias = scale < 0.8; - final scaledWidth = (image.width * scale).round(); - final scaledHeight = (image.height * scale).round(); - final widthOffset = max(0, scaledWidth - requiredWidth) / 2; - final heightOffset = max(0, scaledHeight - requiredHeight) / 2; - - final processedBytes = Float32List(requiredSize); - final buffer = Float32List.view(processedBytes.buffer); - int pixelIndex = 0; - const int greenOff = requiredHeight * requiredWidth; - const int blueOff = 2 * requiredHeight * requiredWidth; - for (var h = 0 + heightOffset; h < scaledHeight - heightOffset; h++) { - for (var w = 0 + widthOffset; w < scaledWidth - widthOffset; w++) { - final RGB pixel = _getPixelBilinear( - w / scale, - h / scale, - image, - rawRgbaBytes, - antiAlias: useAntiAlias, - ); - buffer[pixelIndex] = pixel.$1 / 255; - buffer[pixelIndex + greenOff] = pixel.$2 / 255; - buffer[pixelIndex + blueOff] = pixel.$3 / 255; - pixelIndex++; - } - } - - return processedBytes; -} - Future<(Float32List, List, List, List, Size)> preprocessToMobileFaceNetFloat32List( Dimensions imageDimensions, @@ -424,10 +338,10 @@ RGB _readPixelColor( Uint8List rgbaBytes, ) { if (y < 0 || y >= image.height || x < 0 || x >= image.width) { - if (y < -maxKernelRadius || - y >= image.height + maxKernelRadius || - x < -maxKernelRadius || - x >= image.width + maxKernelRadius) { + if (y < -2 || + y >= image.height + 2 || + x < -2 || + x >= image.width + 2) { _logger.severe( '`readPixelColor`: Invalid pixel coordinates, out of bounds. x: $x, y: $y', ); @@ -445,29 +359,6 @@ RGB _readPixelColor( ); } -RGB _getPixelBlurred( - int x, - int y, - Dimensions image, - Uint8List rgbaBytes, -) { - double r = 0, g = 0, b = 0; - for (int ky = 0; ky < gaussianKernelSize; ky++) { - for (int kx = 0; kx < gaussianKernelSize; kx++) { - final int px = (x - gaussianKernelRadius + kx); - final int py = (y - gaussianKernelRadius + ky); - - final RGB pixelRgbTuple = _readPixelColor(px, py, image, rgbaBytes); - final double weight = gaussianKernel[ky][kx]; - - r += pixelRgbTuple.$1 * weight; - g += pixelRgbTuple.$2 * weight; - b += pixelRgbTuple.$3 * weight; - } - } - return (r.round(), g.round(), b.round()); -} - List> _createGrayscaleIntMatrixFromNormalized2List( Float32List imageList, int startIndex, { @@ -615,52 +506,6 @@ Future _cropAndEncodeCanvas( return await _encodeImageToPng(croppedImage); } -RGB _getPixelBilinear( - num fx, - num fy, - Dimensions image, - Uint8List rawRgbaBytes, { - bool antiAlias = false, -}) { - // Clamp to image boundaries - fx = fx.clamp(0, image.width - 1); - fy = fy.clamp(0, image.height - 1); - - // Get the surrounding coordinates and their weights - final int x0 = fx.floor(); - final int x1 = fx.ceil(); - final int y0 = fy.floor(); - final int y1 = fy.ceil(); - final dx = fx - x0; - final dy = fy - y0; - final dx1 = 1.0 - dx; - final dy1 = 1.0 - dy; - - // Get the original pixels (with gaussian blur if antialias) - final RGB Function(int, int, Dimensions, Uint8List) readPixel = - antiAlias ? _getPixelBlurred : _readPixelColor; - final RGB pixel1 = readPixel(x0, y0, image, rawRgbaBytes); - final RGB pixel2 = readPixel(x1, y0, image, rawRgbaBytes); - final RGB pixel3 = readPixel(x0, y1, image, rawRgbaBytes); - final RGB pixel4 = readPixel(x1, y1, image, rawRgbaBytes); - - int bilinear( - num val1, - num val2, - num val3, - num val4, - ) => - (val1 * dx1 * dy1 + val2 * dx * dy1 + val3 * dx1 * dy + val4 * dx * dy) - .round(); - - // Calculate the weighted sum of pixels - final int r = bilinear(pixel1.$1, pixel2.$1, pixel3.$1, pixel4.$1); - final int g = bilinear(pixel1.$2, pixel2.$2, pixel3.$2, pixel4.$2); - final int b = bilinear(pixel1.$3, pixel2.$3, pixel3.$3, pixel4.$3); - - return (r, g, b); -} - /// Get the pixel value using Bicubic Interpolation. Code taken mainly from https://github.com/brendan-duncan/image/blob/6e407612752ffdb90b28cd5863c7f65856349348/lib/src/image/image.dart#L697 RGB _getPixelBicubic( num fx, @@ -763,31 +608,4 @@ RGB _getPixelBicubic( // final c3 = cubic(dy, ip3, ic3, in3, ia3); return (c0, c1, c2); // (red, green, blue) -} - -List> create2DGaussianKernel(int size, double sigma) { - final List> kernel = - List.generate(size, (_) => List.filled(size, 0)); - double sum = 0.0; - final int center = size ~/ 2; - - for (int y = 0; y < size; y++) { - for (int x = 0; x < size; x++) { - final int dx = x - center; - final int dy = y - center; - final double g = (1 / (2 * pi * sigma * sigma)) * - exp(-(dx * dx + dy * dy) / (2 * sigma * sigma)); - kernel[y][x] = g; - sum += g; - } - } - - // Normalize the kernel - for (int y = 0; y < size; y++) { - for (int x = 0; x < size; x++) { - kernel[y][x] /= sum; - } - } - - return kernel; -} +} \ No newline at end of file diff --git a/mobile/lib/utils/ml_util.dart b/mobile/lib/utils/ml_util.dart index 829fb4bcb7..c2280f71fb 100644 --- a/mobile/lib/utils/ml_util.dart +++ b/mobile/lib/utils/ml_util.dart @@ -415,9 +415,9 @@ Future analyzeImageStatic(Map args) async { if (decodeInRust) { rustResults = await processImageMlFromPath(imagePath: imagePath); } else { - final (image, rawRgbaBytes) = await decodeImageFromPath(imagePath); + final (image, decodedRgbaBytes) = await decodeImageFromPath(imagePath); rustResults = await processImageMlFromData( - rgbaData: rawRgbaBytes, + rgbaData: decodedRgbaBytes, width: image.width, height: image.height, );