mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-02-24 10:28:50 +01:00
Add enhancement: portrait blur with deeplab
This commit is contained in:
parent
0c400786e0
commit
762fe521b2
12 changed files with 274 additions and 12 deletions
|
@ -8,3 +8,5 @@ const homeFolderNotFoundUrl =
|
||||||
"https://gitlab.com/nkming2/nc-photos/-/wikis/help/home-folder-not-found";
|
"https://gitlab.com/nkming2/nc-photos/-/wikis/help/home-folder-not-found";
|
||||||
const enhanceZeroDceUrl =
|
const enhanceZeroDceUrl =
|
||||||
"https://gitlab.com/nkming2/nc-photos/-/wikis/help/enhance/zero-dce";
|
"https://gitlab.com/nkming2/nc-photos/-/wikis/help/enhance/zero-dce";
|
||||||
|
const enhanceDeepLabPortraitBlurUrl =
|
||||||
|
"https://gitlab.com/nkming2/nc-photos/-/wikis/help/enhance/portrait-blur-(deeplab)";
|
||||||
|
|
|
@ -1187,6 +1187,10 @@
|
||||||
"@deletePermanentlyLocalConfirmationDialogContent": {
|
"@deletePermanentlyLocalConfirmationDialogContent": {
|
||||||
"description": "Make sure the user wants to delete the items from the current device"
|
"description": "Make sure the user wants to delete the items from the current device"
|
||||||
},
|
},
|
||||||
|
"enhancePortraitBlurTitle": "Portrait blur",
|
||||||
|
"@enhancePortraitBlurTitle": {
|
||||||
|
"description": "Blur the background of a photo"
|
||||||
|
},
|
||||||
|
|
||||||
"errorUnauthenticated": "Unauthenticated access. Please sign-in again if the problem continues",
|
"errorUnauthenticated": "Unauthenticated access. Please sign-in again if the problem continues",
|
||||||
"@errorUnauthenticated": {
|
"@errorUnauthenticated": {
|
||||||
|
|
|
@ -88,6 +88,7 @@
|
||||||
"enhanceLowLightTitle",
|
"enhanceLowLightTitle",
|
||||||
"collectionEnhancedPhotosLabel",
|
"collectionEnhancedPhotosLabel",
|
||||||
"deletePermanentlyLocalConfirmationDialogContent",
|
"deletePermanentlyLocalConfirmationDialogContent",
|
||||||
|
"enhancePortraitBlurTitle",
|
||||||
"errorAlbumDowngrade"
|
"errorAlbumDowngrade"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -194,6 +195,7 @@
|
||||||
"enhanceLowLightTitle",
|
"enhanceLowLightTitle",
|
||||||
"collectionEnhancedPhotosLabel",
|
"collectionEnhancedPhotosLabel",
|
||||||
"deletePermanentlyLocalConfirmationDialogContent",
|
"deletePermanentlyLocalConfirmationDialogContent",
|
||||||
|
"enhancePortraitBlurTitle",
|
||||||
"errorAlbumDowngrade"
|
"errorAlbumDowngrade"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -355,6 +357,7 @@
|
||||||
"enhanceLowLightTitle",
|
"enhanceLowLightTitle",
|
||||||
"collectionEnhancedPhotosLabel",
|
"collectionEnhancedPhotosLabel",
|
||||||
"deletePermanentlyLocalConfirmationDialogContent",
|
"deletePermanentlyLocalConfirmationDialogContent",
|
||||||
|
"enhancePortraitBlurTitle",
|
||||||
"errorAlbumDowngrade"
|
"errorAlbumDowngrade"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -366,14 +369,16 @@
|
||||||
"enhanceTooltip",
|
"enhanceTooltip",
|
||||||
"enhanceLowLightTitle",
|
"enhanceLowLightTitle",
|
||||||
"collectionEnhancedPhotosLabel",
|
"collectionEnhancedPhotosLabel",
|
||||||
"deletePermanentlyLocalConfirmationDialogContent"
|
"deletePermanentlyLocalConfirmationDialogContent",
|
||||||
|
"enhancePortraitBlurTitle"
|
||||||
],
|
],
|
||||||
|
|
||||||
"fi": [
|
"fi": [
|
||||||
"enhanceTooltip",
|
"enhanceTooltip",
|
||||||
"enhanceLowLightTitle",
|
"enhanceLowLightTitle",
|
||||||
"collectionEnhancedPhotosLabel",
|
"collectionEnhancedPhotosLabel",
|
||||||
"deletePermanentlyLocalConfirmationDialogContent"
|
"deletePermanentlyLocalConfirmationDialogContent",
|
||||||
|
"enhancePortraitBlurTitle"
|
||||||
],
|
],
|
||||||
|
|
||||||
"fr": [
|
"fr": [
|
||||||
|
@ -384,7 +389,8 @@
|
||||||
"enhanceTooltip",
|
"enhanceTooltip",
|
||||||
"enhanceLowLightTitle",
|
"enhanceLowLightTitle",
|
||||||
"collectionEnhancedPhotosLabel",
|
"collectionEnhancedPhotosLabel",
|
||||||
"deletePermanentlyLocalConfirmationDialogContent"
|
"deletePermanentlyLocalConfirmationDialogContent",
|
||||||
|
"enhancePortraitBlurTitle"
|
||||||
],
|
],
|
||||||
|
|
||||||
"pl": [
|
"pl": [
|
||||||
|
@ -412,20 +418,23 @@
|
||||||
"enhanceTooltip",
|
"enhanceTooltip",
|
||||||
"enhanceLowLightTitle",
|
"enhanceLowLightTitle",
|
||||||
"collectionEnhancedPhotosLabel",
|
"collectionEnhancedPhotosLabel",
|
||||||
"deletePermanentlyLocalConfirmationDialogContent"
|
"deletePermanentlyLocalConfirmationDialogContent",
|
||||||
|
"enhancePortraitBlurTitle"
|
||||||
],
|
],
|
||||||
|
|
||||||
"pt": [
|
"pt": [
|
||||||
"enhanceTooltip",
|
"enhanceTooltip",
|
||||||
"enhanceLowLightTitle",
|
"enhanceLowLightTitle",
|
||||||
"collectionEnhancedPhotosLabel",
|
"collectionEnhancedPhotosLabel",
|
||||||
"deletePermanentlyLocalConfirmationDialogContent"
|
"deletePermanentlyLocalConfirmationDialogContent",
|
||||||
|
"enhancePortraitBlurTitle"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ru": [
|
"ru": [
|
||||||
"enhanceTooltip",
|
"enhanceTooltip",
|
||||||
"enhanceLowLightTitle",
|
"enhanceLowLightTitle",
|
||||||
"collectionEnhancedPhotosLabel",
|
"collectionEnhancedPhotosLabel",
|
||||||
"deletePermanentlyLocalConfirmationDialogContent"
|
"deletePermanentlyLocalConfirmationDialogContent",
|
||||||
|
"enhancePortraitBlurTitle"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,6 +72,16 @@ class EnhanceHandler {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case _Algorithm.deepLab3Portrait:
|
||||||
|
await ImageProcessor.deepLab3Portrait(
|
||||||
|
"${account.url}/${file.path}",
|
||||||
|
file.filename,
|
||||||
|
headers: {
|
||||||
|
"Authorization": Api.getAuthorizationHeaderValue(account),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,6 +115,13 @@ class EnhanceHandler {
|
||||||
link: enhanceZeroDceUrl,
|
link: enhanceZeroDceUrl,
|
||||||
algorithm: _Algorithm.zeroDce,
|
algorithm: _Algorithm.zeroDce,
|
||||||
),
|
),
|
||||||
|
if (platform_k.isAndroid)
|
||||||
|
_Option(
|
||||||
|
title: L10n.global().enhancePortraitBlurTitle,
|
||||||
|
subtitle: "DeepLap v3",
|
||||||
|
link: enhanceDeepLabPortraitBlurUrl,
|
||||||
|
algorithm: _Algorithm.deepLab3Portrait,
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
final Account account;
|
final Account account;
|
||||||
|
@ -115,6 +132,7 @@ class EnhanceHandler {
|
||||||
|
|
||||||
enum _Algorithm {
|
enum _Algorithm {
|
||||||
zeroDce,
|
zeroDce,
|
||||||
|
deepLab3Portrait,
|
||||||
}
|
}
|
||||||
|
|
||||||
class _Option {
|
class _Option {
|
||||||
|
|
|
@ -18,6 +18,7 @@ rootProject.allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
maven { url 'https://jitpack.io' }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,6 +56,7 @@ dependencies {
|
||||||
implementation "androidx.annotation:annotation:1.3.0"
|
implementation "androidx.annotation:annotation:1.3.0"
|
||||||
implementation "androidx.core:core-ktx:1.7.0"
|
implementation "androidx.core:core-ktx:1.7.0"
|
||||||
implementation "androidx.exifinterface:exifinterface:1.3.3"
|
implementation "androidx.exifinterface:exifinterface:1.3.3"
|
||||||
|
implementation 'com.github.android:renderscript-intrinsics-replacement-toolkit:b6363490c3'
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
implementation 'org.tensorflow:tensorflow-lite:2.8.0'
|
implementation 'org.tensorflow:tensorflow-lite:2.8.0'
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
|
@ -30,6 +30,19 @@ class ImageProcessorChannelHandler(context: Context) :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"deepLab3Portrait" -> {
|
||||||
|
try {
|
||||||
|
deepLab3Portrait(
|
||||||
|
call.argument("fileUrl")!!,
|
||||||
|
call.argument("headers"),
|
||||||
|
call.argument("filename")!!,
|
||||||
|
result
|
||||||
|
)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
result.error("systemException", e.toString(), null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
else -> result.notImplemented()
|
else -> result.notImplemented()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,12 +58,25 @@ class ImageProcessorChannelHandler(context: Context) :
|
||||||
private fun zeroDce(
|
private fun zeroDce(
|
||||||
fileUrl: String, headers: Map<String, String>?, filename: String,
|
fileUrl: String, headers: Map<String, String>?, filename: String,
|
||||||
result: MethodChannel.Result
|
result: MethodChannel.Result
|
||||||
|
) = method(
|
||||||
|
fileUrl, headers, filename, ImageProcessorService.METHOD_ZERO_DCE,
|
||||||
|
result
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun deepLab3Portrait(
|
||||||
|
fileUrl: String, headers: Map<String, String>?, filename: String,
|
||||||
|
result: MethodChannel.Result
|
||||||
|
) = method(
|
||||||
|
fileUrl, headers, filename,
|
||||||
|
ImageProcessorService.METHOD_DEEL_LAP_PORTRAIT, result
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun method(
|
||||||
|
fileUrl: String, headers: Map<String, String>?, filename: String,
|
||||||
|
method: String, result: MethodChannel.Result
|
||||||
) {
|
) {
|
||||||
val intent = Intent(context, ImageProcessorService::class.java).apply {
|
val intent = Intent(context, ImageProcessorService::class.java).apply {
|
||||||
putExtra(
|
putExtra(ImageProcessorService.EXTRA_METHOD, method)
|
||||||
ImageProcessorService.EXTRA_METHOD,
|
|
||||||
ImageProcessorService.METHOD_ZERO_DCE
|
|
||||||
)
|
|
||||||
putExtra(ImageProcessorService.EXTRA_FILE_URL, fileUrl)
|
putExtra(ImageProcessorService.EXTRA_FILE_URL, fileUrl)
|
||||||
putExtra(
|
putExtra(
|
||||||
ImageProcessorService.EXTRA_HEADERS,
|
ImageProcessorService.EXTRA_HEADERS,
|
||||||
|
|
|
@ -18,6 +18,7 @@ import androidx.core.app.NotificationChannelCompat
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.exifinterface.media.ExifInterface
|
import androidx.exifinterface.media.ExifInterface
|
||||||
|
import com.nkming.nc_photos.plugin.image_processor.DeepLab3Portrait
|
||||||
import com.nkming.nc_photos.plugin.image_processor.ZeroDce
|
import com.nkming.nc_photos.plugin.image_processor.ZeroDce
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.HttpURLConnection
|
import java.net.HttpURLConnection
|
||||||
|
@ -27,6 +28,7 @@ class ImageProcessorService : Service() {
|
||||||
companion object {
|
companion object {
|
||||||
const val EXTRA_METHOD = "method"
|
const val EXTRA_METHOD = "method"
|
||||||
const val METHOD_ZERO_DCE = "zero-dce"
|
const val METHOD_ZERO_DCE = "zero-dce"
|
||||||
|
const val METHOD_DEEL_LAP_PORTRAIT = "DeepLab3Portrait"
|
||||||
const val EXTRA_FILE_URL = "fileUrl"
|
const val EXTRA_FILE_URL = "fileUrl"
|
||||||
const val EXTRA_HEADERS = "headers"
|
const val EXTRA_HEADERS = "headers"
|
||||||
const val EXTRA_FILENAME = "filename"
|
const val EXTRA_FILENAME = "filename"
|
||||||
|
@ -91,6 +93,9 @@ class ImageProcessorService : Service() {
|
||||||
val method = intent.getStringExtra(EXTRA_METHOD)
|
val method = intent.getStringExtra(EXTRA_METHOD)
|
||||||
when (method) {
|
when (method) {
|
||||||
METHOD_ZERO_DCE -> onZeroDce(startId, intent.extras!!)
|
METHOD_ZERO_DCE -> onZeroDce(startId, intent.extras!!)
|
||||||
|
METHOD_DEEL_LAP_PORTRAIT -> onDeepLapPortrait(
|
||||||
|
startId, intent.extras!!
|
||||||
|
)
|
||||||
else -> {
|
else -> {
|
||||||
Log.e(TAG, "Unknown method: $method")
|
Log.e(TAG, "Unknown method: $method")
|
||||||
// we can't call stopSelf here as it'll stop the service even if
|
// we can't call stopSelf here as it'll stop the service even if
|
||||||
|
@ -100,7 +105,20 @@ class ImageProcessorService : Service() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onZeroDce(startId: Int, extras: Bundle) {
|
private fun onZeroDce(startId: Int, extras: Bundle) =
|
||||||
|
onMethod(startId, extras, METHOD_ZERO_DCE)
|
||||||
|
|
||||||
|
private fun onDeepLapPortrait(startId: Int, extras: Bundle) =
|
||||||
|
onMethod(startId, extras, METHOD_DEEL_LAP_PORTRAIT)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle methods without arguments
|
||||||
|
*
|
||||||
|
* @param startId
|
||||||
|
* @param extras
|
||||||
|
* @param method
|
||||||
|
*/
|
||||||
|
private fun onMethod(startId: Int, extras: Bundle, method: String) {
|
||||||
val fileUrl = extras.getString(EXTRA_FILE_URL)!!
|
val fileUrl = extras.getString(EXTRA_FILE_URL)!!
|
||||||
|
|
||||||
@Suppress("Unchecked_cast")
|
@Suppress("Unchecked_cast")
|
||||||
|
@ -109,7 +127,7 @@ class ImageProcessorService : Service() {
|
||||||
val filename = extras.getString(EXTRA_FILENAME)!!
|
val filename = extras.getString(EXTRA_FILENAME)!!
|
||||||
addCommand(
|
addCommand(
|
||||||
ImageProcessorCommand(
|
ImageProcessorCommand(
|
||||||
startId, METHOD_ZERO_DCE, fileUrl, headers, filename
|
startId, method, fileUrl, headers, filename
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -396,6 +414,9 @@ private open class ImageProcessorCommandTask(context: Context) :
|
||||||
ImageProcessorService.METHOD_ZERO_DCE -> ZeroDce(context).infer(
|
ImageProcessorService.METHOD_ZERO_DCE -> ZeroDce(context).infer(
|
||||||
fileUri
|
fileUri
|
||||||
)
|
)
|
||||||
|
ImageProcessorService.METHOD_DEEL_LAP_PORTRAIT -> DeepLab3Portrait(
|
||||||
|
context
|
||||||
|
).infer(fileUri)
|
||||||
else -> throw IllegalArgumentException(
|
else -> throw IllegalArgumentException(
|
||||||
"Unknown method: ${cmd.method}"
|
"Unknown method: ${cmd.method}"
|
||||||
)
|
)
|
||||||
|
|
|
@ -22,3 +22,7 @@ inline fun <T> HttpURLConnection.use(block: (HttpURLConnection) -> T): T {
|
||||||
disconnect()
|
disconnect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fun ByteArray.transform(transform: (Byte) -> Byte) {
|
||||||
|
forEachIndexed{ i, v -> this[i] = transform(v) }
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,150 @@
|
||||||
|
package com.nkming.nc_photos.plugin.image_processor
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.*
|
||||||
|
import android.net.Uri
|
||||||
|
import android.util.Log
|
||||||
|
import com.google.android.renderscript.Toolkit
|
||||||
|
import com.nkming.nc_photos.plugin.BitmapResizeMethod
|
||||||
|
import com.nkming.nc_photos.plugin.BitmapUtil
|
||||||
|
import com.nkming.nc_photos.plugin.transform
|
||||||
|
import org.tensorflow.lite.Interpreter
|
||||||
|
import java.io.File
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.FloatBuffer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DeepLab is a state-of-art deep learning model for semantic image
|
||||||
|
* segmentation, where the goal is to assign semantic labels (e.g., person, dog,
|
||||||
|
* cat and so on) to every pixel in the input image
|
||||||
|
*
|
||||||
|
* See: https://github.com/tensorflow/models/tree/master/research/deeplab
|
||||||
|
*/
|
||||||
|
private class DeepLab3(context: Context) {
|
||||||
|
companion object {
|
||||||
|
private const val MODEL = "lite-model_mobilenetv2-dm05-coco_dr_1.tflite"
|
||||||
|
const val WIDTH = 513
|
||||||
|
const val HEIGHT = 513
|
||||||
|
|
||||||
|
private const val TAG = "DeepLab3"
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Label(val value: Int) {
|
||||||
|
BACKGROUND(0),
|
||||||
|
AEROPLANE(1),
|
||||||
|
BICYCLE(2),
|
||||||
|
BIRD(3),
|
||||||
|
BOAT(4),
|
||||||
|
BOTTLE(5),
|
||||||
|
BUS(6),
|
||||||
|
CAR(7),
|
||||||
|
CAT(8),
|
||||||
|
CHAIR(9),
|
||||||
|
COW(10),
|
||||||
|
DINING_TABLE(11),
|
||||||
|
DOG(12),
|
||||||
|
HORSE(13),
|
||||||
|
MOTORBIKE(14),
|
||||||
|
PERSON(15),
|
||||||
|
POTTED_PLANT(16),
|
||||||
|
SHEEP(17),
|
||||||
|
SOFA(18),
|
||||||
|
TRAIN(19),
|
||||||
|
TV(20),
|
||||||
|
}
|
||||||
|
|
||||||
|
fun infer(imageUri: Uri): ByteBuffer {
|
||||||
|
val interpreter =
|
||||||
|
Interpreter(TfLiteHelper.loadModelFromAsset(context, MODEL))
|
||||||
|
interpreter.allocateTensors()
|
||||||
|
|
||||||
|
Log.i(TAG, "Converting bitmap to input")
|
||||||
|
val inputBitmap =
|
||||||
|
BitmapUtil.loadImageFixed(context, imageUri, WIDTH, HEIGHT)
|
||||||
|
val input = TfLiteHelper.bitmapToRgbFloatArray(inputBitmap)
|
||||||
|
val output = FloatBuffer.allocate(WIDTH * HEIGHT * Label.values().size)
|
||||||
|
Log.i(TAG, "Inferring")
|
||||||
|
interpreter.run(input, output)
|
||||||
|
return TfLiteHelper.argmax(output, WIDTH, HEIGHT, Label.values().size)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val context = context
|
||||||
|
}
|
||||||
|
|
||||||
|
class DeepLab3Portrait(context: Context) {
|
||||||
|
companion object {
|
||||||
|
private const val RADIUS = 16
|
||||||
|
private const val MAX_WIDTH = 2048
|
||||||
|
private const val MAX_HEIGHT = 1536
|
||||||
|
|
||||||
|
private const val TAG = "DeepLab3Portrait"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun infer(imageUri: Uri): Bitmap {
|
||||||
|
val segmentMap = deepLab.infer(imageUri).also {
|
||||||
|
postProcessSegmentMap(it)
|
||||||
|
}
|
||||||
|
return enhance(imageUri, segmentMap, RADIUS)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post-process the segment map.
|
||||||
|
*
|
||||||
|
* The resulting segment map will:
|
||||||
|
* 1. Contain only the most significant label (the one with the most pixel)
|
||||||
|
* 2. The label value set to 255
|
||||||
|
* 3. The background set to 0
|
||||||
|
*
|
||||||
|
* @param segmentMap
|
||||||
|
*/
|
||||||
|
private fun postProcessSegmentMap(segmentMap: ByteBuffer) {
|
||||||
|
// keep only the largest segment
|
||||||
|
val count = mutableMapOf<Byte, Int>()
|
||||||
|
segmentMap.array().forEach {
|
||||||
|
if (it != DeepLab3.Label.BACKGROUND.value.toByte()) {
|
||||||
|
count[it] = (count[it] ?: 0) + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val keep = count.maxByOrNull { it.value }?.key
|
||||||
|
segmentMap.array().transform { if (it == keep) 0xFF.toByte() else 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun enhance(
|
||||||
|
imageUri: Uri, segmentMap: ByteBuffer, radius: Int
|
||||||
|
): Bitmap {
|
||||||
|
Log.i(TAG, "[enhance] Enhancing image")
|
||||||
|
// downscale original to prevent OOM
|
||||||
|
val orig = BitmapUtil.loadImage(
|
||||||
|
context, imageUri, MAX_WIDTH, MAX_HEIGHT, BitmapResizeMethod.FIT,
|
||||||
|
isAllowSwapSide = true, shouldUpscale = false
|
||||||
|
)
|
||||||
|
val bg = Toolkit.blur(orig, radius)
|
||||||
|
|
||||||
|
var alpha = Bitmap.createBitmap(
|
||||||
|
DeepLab3.WIDTH, DeepLab3.HEIGHT, Bitmap.Config.ALPHA_8
|
||||||
|
)
|
||||||
|
alpha.copyPixelsFromBuffer(segmentMap)
|
||||||
|
alpha = Bitmap.createScaledBitmap(alpha, orig.width, orig.height, true)
|
||||||
|
// blur the mask to smoothen the edge
|
||||||
|
alpha = Toolkit.blur(alpha, 16)
|
||||||
|
File(context.filesDir, "alpha.png").outputStream().use {
|
||||||
|
alpha.compress(Bitmap.CompressFormat.PNG, 50, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
val shader = ComposeShader(
|
||||||
|
BitmapShader(orig, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP),
|
||||||
|
BitmapShader(alpha, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP),
|
||||||
|
PorterDuff.Mode.DST_ATOP
|
||||||
|
)
|
||||||
|
val paint = Paint().apply {
|
||||||
|
setShader(shader)
|
||||||
|
}
|
||||||
|
Canvas(bg).apply {
|
||||||
|
drawRect(0f, 0f, orig.width.toFloat(), orig.height.toFloat(), paint)
|
||||||
|
}
|
||||||
|
return bg
|
||||||
|
}
|
||||||
|
|
||||||
|
private val context = context
|
||||||
|
private val deepLab = DeepLab3(context)
|
||||||
|
}
|
|
@ -88,5 +88,20 @@ interface TfLiteHelper {
|
||||||
outputBitmap.copyPixelsFromBuffer(buffer)
|
outputBitmap.copyPixelsFromBuffer(buffer)
|
||||||
return outputBitmap
|
return outputBitmap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun argmax(
|
||||||
|
output: FloatBuffer, width: Int, height: Int, channel: Int
|
||||||
|
): ByteBuffer {
|
||||||
|
val product = ByteBuffer.allocate(width * height)
|
||||||
|
val array = output.array()
|
||||||
|
var j = 0
|
||||||
|
for (i in 0 until width * height) {
|
||||||
|
val pixel = array.slice(j until j + channel)
|
||||||
|
val max = pixel.indices.maxByOrNull { pixel[it] }!!
|
||||||
|
product.put(i, max.toByte())
|
||||||
|
j += channel
|
||||||
|
}
|
||||||
|
return product
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,17 @@ class ImageProcessor {
|
||||||
"filename": filename,
|
"filename": filename,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
static Future<void> deepLab3Portrait(
|
||||||
|
String fileUrl,
|
||||||
|
String filename, {
|
||||||
|
Map<String, String>? headers,
|
||||||
|
}) =>
|
||||||
|
_methodChannel.invokeMethod("deepLab3Portrait", <String, dynamic>{
|
||||||
|
"fileUrl": fileUrl,
|
||||||
|
"headers": headers,
|
||||||
|
"filename": filename,
|
||||||
|
});
|
||||||
|
|
||||||
static const _methodChannel =
|
static const _methodChannel =
|
||||||
MethodChannel("${k.libId}/image_processor_method");
|
MethodChannel("${k.libId}/image_processor_method");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue