GAN TFLite模型处理Bitmap分块融合后边缘过暗问题排查
问题描述
使用仅支持512×512像素的GAN TFLite模型处理Bitmap时,先将图片裁剪为分块批量处理,结果图片出现大量接缝。添加边缘融合代码后,边缘变得异常黑暗,相关Android代码及效果截图如下:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); imageView = findViewById(R.id.imageView); picture = findViewById(R.id.button); MIRNetConverter.initialize(MainActivity.this); uploadButton = findViewById(R.id.upload); uploadButton.setOnClickListener(new View.OnClickListener() { @RequiresApi(api = Build.VERSION_CODES.TIRAMISU) @Override public void onClick(View view) { if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_MEDIA_IMAGES}, 1); } else { uploadImage(); } } }); processingDialog = new Dialog(this); processingDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); processingDialog.setCancelable(false); processingDialog.setContentView(R.layout.processing_screen); executorService = Executors.newFixedThreadPool(4); } @Override protected void onDestroy() { super.onDestroy(); if (executorService != null) { executorService.shutdown(); } } private void showProcessingDialog() { if (processingDialog != null && !processingDialog.isShowing()) { ProgressBar progressBar = processingDialog.findViewById(R.id.progressBar); TextView textView = processingDialog.findViewById(R.id.textView); if (progressBar != null && textView != null) { progressBar.setIndeterminate(true); textView.setText("Processing..."); } processingDialog.show(); } } private void uploadImage() { Intent galleryIntent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); startActivityForResult(galleryIntent, 2); } private void hideProcessingDialog() { if (processingDialog != null && processingDialog.isShowing()) { processingDialog.dismiss(); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == RESULT_OK) { if (requestCode == 1 && data != null) { Bitmap image = (Bitmap) data.getExtras().get("data"); imageView.setImageBitmap(image); showProcessingDialog(); processImage(image); } else if (requestCode == 2 && data != null) { try { Bitmap image = MediaStore.Images.Media.getBitmap(this.getContentResolver(), data.getData()); imageView.setImageBitmap(image); showProcessingDialog(); processImage(image); } catch (IOException e) { e.printStackTrace(); } } } } private void processImage(Bitmap image) { executorService.execute(new Runnable() { @Override public void run() { int width = image.getWidth(); int height = image.getHeight(); Bitmap resultBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); int numTilesX = (int) Math.ceil((float) width / (TILE_SIZE - OVERLAP)); int numTilesY = (int) Math.ceil((float) height / (TILE_SIZE - OVERLAP)); for (int y = 0; y < numTilesY; y++) { for (int x = 0; x < numTilesX; x++) { int startX = x * (TILE_SIZE - OVERLAP); int startY = y * (TILE_SIZE - OVERLAP); int endX = Math.min(startX + TILE_SIZE, width); int endY = Math.min(startY + TILE_SIZE, height); startX = Math.max(startX, 0); startY = Math.max(startY, 0); Bitmap tileBitmap = Bitmap.createBitmap(image, startX, startY, endX - startX, endY - startY); Bitmap processedTile = processTile(tileBitmap); int drawX = x * (TILE_SIZE - OVERLAP); int drawY = y * (TILE_SIZE - OVERLAP); blendTiles(resultBitmap, processedTile, drawX, drawY, startX, startY, endX, endY); } } runOnUiThread(new Runnable() { @Override public void run() { imageView.setImageBitmap(resultBitmap); hideProcessingDialog(); } }); } }); } private void blendTiles(Bitmap result, Bitmap tile, int drawX, int drawY, int startX, int startY, int endX, int endY) { Canvas canvas = new Canvas(result); Paint paint = new Paint(); paint.setAntiAlias(true); int tileWidth = tile.getWidth(); int tileHeight = tile.getHeight(); for (int i = 0; i < tileWidth; i++) { for (int j = 0; j < tileHeight; j++) { int tilePixel = tile.getPixel(i, j); int resultPixelX = drawX + i; int resultPixelY = drawY + j; if (resultPixelX < endX && resultPixelY < endY) { int resultPixel = result.getPixel(resultPixelX, resultPixelY); float alpha = calculateAlpha(i, j, tileWidth, tileHeight); int blendedPixel = blendPixels(resultPixel, tilePixel, alpha); result.setPixel(resultPixelX, resultPixelY, blendedPixel); } } } } private float calculateAlpha(int x, int y, int width, int height) { float edgeX = (x < OVERLAP) ? (x / (float) OVERLAP) : ((width - x - 1) < OVERLAP) ? ((width - x - 1) / (float) OVERLAP) : 1.0f; float edgeY = (y < OVERLAP) ? (y / (float) OVERLAP) : ((height - y - 1) < OVERLAP) ? ((height - y - 1) / (float) OVERLAP) : 1.0f; return Math.min(edgeX, edgeY); } private int blendPixels(int basePixel, int topPixel, float alpha) { int baseAlpha = Color.alpha(basePixel); int baseRed = Color.red(basePixel); int baseGreen = Color.green(basePixel); int baseBlue = Color.blue(basePixel); int topAlpha = Color.alpha(topPixel); int topRed = Color.red(topPixel); int topGreen = Color.green(topPixel); int topBlue = Color.blue(topPixel); int blendedAlpha = (int) (baseAlpha * (1 - alpha) + topAlpha * alpha); int blendedRed = (int) (baseRed * (1 - alpha) + topRed * alpha); int blendedGreen = (int) (baseGreen * (1 - alpha) + topGreen * alpha); int blendedBlue = (int) (baseBlue * (1 - alpha) + topBlue * alpha); return Color.argb(blendedAlpha, blendedRed, blendedGreen, blendedBlue); } private Bitmap processTile(Bitmap tile) { Bitmap resizedTile = Bitmap.createScaledBitmap(tile, TILE_SIZE, TILE_SIZE, true); Bitmap normalTile = MIRNetConverter.convertToNormal(MainActivity.this, resizedTile); return Bitmap.createScaledBitmap(normalTile, tile.getWidth(), tile.getHeight(), true); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == 100) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { startCamera(); } } else if (requestCode == 1) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { uploadImage(); } else { Toast.makeText(this, "Permission denied to read images from media storage", Toast.LENGTH_SHORT).show(); } } }

问题原因及修复方案
一、核心问题分析
1. 初始结果Bitmap为黑色透明,混合时引入暗部
Bitmap.createBitmap创建的resultBitmap默认像素是黑色透明(ARGB=0,0,0,0),处理第一个Tile时,重叠区域的混合逻辑会将处理后的Tile像素与黑色底色混合,直接拉低亮度导致边缘发黑。
2. 混合逻辑未区分首次绘制与重叠绘制
所有Tile都执行相同的混合逻辑,但第一个Tile没有前置已处理像素,不需要混合,直接写入即可;后续Tile仅在与已绘制区域重叠的部分才需要混合。
3. Alpha过渡逻辑方向错误
当前calculateAlpha在Tile边缘返回的权重值,会让重叠区域的Tile像素权重过低,进一步加剧黑色底色的影响。
二、具体修复代码
1. 初始化结果Bitmap为原图副本(避免黑色底色)
修改processImage中的初始化代码:
// 原代码 Bitmap resultBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); // 修改为 Bitmap resultBitmap = image.copy(Bitmap.Config.ARGB_8888, true);
2. 优化混合逻辑,区分首次与重叠绘制
修改blendTiles方法,跳过无前置像素的区域混合:
private void blendTiles(Bitmap result, Bitmap tile, int drawX, int drawY, int startX, int startY, int endX, int endY) { int tileWidth = tile.getWidth(); int tileHeight = tile.getHeight(); for (int i = 0; i < tileWidth; i++) { for (int j = 0; j < tileHeight; j++) { int tilePixel = tile.getPixel(i, j); int resultPixelX = drawX + i; int resultPixelY = drawY + j; if (resultPixelX < endX && resultPixelY < endY) { // 判断当前位置是否是首次绘制(无前置已处理像素) boolean isFirstDraw = (drawX == 0 && i < (TILE_SIZE - OVERLAP)) || (drawY == 0 && j < (TILE_SIZE - OVERLAP)) || (resultPixelX >= (drawX + OVERLAP) && resultPixelY >= (drawY + OVERLAP)); if (isFirstDraw) { // 首次绘制直接写入Tile像素 result.setPixel(resultPixelX, resultPixelY, tilePixel); } else { // 重叠区域执行混合 int resultPixel = result.getPixel(resultPixelX, resultPixelY); float alpha = calculateAlpha(i, j, tileWidth, tileHeight); int blendedPixel = blendPixels(resultPixel, tilePixel, alpha); result.setPixel(resultPixelX, resultPixelY, blendedPixel); } } } } }
3. 修正Alpha过渡方向(让重叠区域过渡自然)
修改calculateAlpha方法,让边缘区域的当前Tile权重从0平滑过渡到1:
private float calculateAlpha(int x, int y, int width, int height) { float edgeX = 1.0f; // 左边缘重叠区:x从0到OVERLAP,权重从0到1 if (x < OVERLAP) { edgeX = x / (float) OVERLAP; } // 右边缘重叠区:x从width-OVERLAP到width-1,权重从1到0(让下一个Tile的过渡更自然) else if ((width - x - 1) < OVERLAP) { edgeX = 1.0f - ((width - x - 1) / (float) OVERLAP); } float edgeY = 1.0f; if (y < OVERLAP) { edgeY = y / (float) OVERLAP; } else if ((height - y - 1) < OVERLAP) { edgeY = 1.0f - ((height - y - 1) / (float) OVERLAP); } // 取X和Y方向的最大权重,确保角落区域过渡顺滑 return Math.max(edgeX, edgeY); }
4. 优化混合算法(避免Alpha通道异常)
确保混合时Alpha通道正确叠加,修改blendPixels:
private int blendPixels(int basePixel, int topPixel, float alpha) { // 处理极端情况,避免无效计算 if (Color.alpha(topPixel) == 0) return basePixel; if (alpha >= 1.0f) return topPixel; if (alpha <= 0.0f) return basePixel; float invAlpha = 1.0f - alpha; int baseRed = Color.red(basePixel); int baseGreen = Color.green(basePixel); int baseBlue = Color.blue(basePixel); int topRed = Color.red(topPixel); int topGreen = Color.green(topPixel); int topBlue = Color.blue(topPixel); // 仅混合RGB通道,Alpha通道取最大值(避免透明化) int blendedRed = (int) (baseRed * invAlpha + topRed * alpha); int blendedGreen = (int) (baseGreen * invAlpha + topGreen * alpha); int blendedBlue = (int) (baseBlue * invAlpha + topBlue * alpha); int blendedAlpha = Math.max(Color.alpha(basePixel), Color.alpha(topPixel)); return Color.argb(blendedAlpha, blendedRed, blendedGreen, blendedBlue); }
内容的提问来源于stack exchange,提问作者Doctor Pizza
相关产品推荐
相关产品推荐

