You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 21:55:55