简体   繁体   中英

Android: Really bad image quality when saving bitmap to sdcard

I am making an OCR app for Android, that will take a screenshot of some text, recognise it and search a key word on Google. If you haven't already realized, I'm trying to make a "Google Now on Tap" clone.

To make the OCR work better, I am first rotating the image, then filtering the image. First by getting rid of the status bar and the navigation bar, then converting it to grayscale, then sharpening.

But the image quality after filtering the image is extremely pixelated, and this greatly effects OCR accuracy.

Here are the images, before and after (just of an IFTTT email I got) 过滤之前过滤后

As you can see, the before image is much higher quality than the filtered and rotated one.

Here is my code for rotating, filtering and saving the image:

Firstly taking screenshot, then saving the screenshot.

    public void getScreenshot()
    {
        try
        {
            Process sh = Runtime.getRuntime().exec("su", null, null);

            OutputStream os = sh.getOutputStream();
            os.write(("/system/bin/screencap -p " + _path).getBytes("ASCII"));
            os.flush();

            os.close();
            sh.waitFor();

            onPhotoTaken();

            Toast.makeText(this, "Screenshot taken", Toast.LENGTH_SHORT).show();
        }
        catch (IOException e)
        {
            System.out.println("IOException");
        }
        catch (InterruptedException e)
        {
            System.out.println("InterruptedException");
        }

    }

Then, rotate the image:

 protected void onPhotoTaken() { _taken = true; BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 4; Bitmap bitmap = BitmapFactory.decodeFile(_path, options); try { ExifInterface exif = new ExifInterface(_path); int exifOrientation = exif.getAttributeInt( ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); Log.v(TAG, "Orient: " + exifOrientation); int rotate = 0; switch (exifOrientation) { case ExifInterface.ORIENTATION_ROTATE_90: rotate = 90; break; case ExifInterface.ORIENTATION_ROTATE_180: rotate = 180; break; case ExifInterface.ORIENTATION_ROTATE_270: rotate = 270; break; } Log.v(TAG, "Rotation: " + rotate); if (rotate != 0) { // Getting width & height of the given image. int w = bitmap.getWidth(); int h = bitmap.getHeight(); // Setting pre rotate Matrix mtx = new Matrix(); mtx.preRotate(rotate); // Rotating Bitmap bitmap = Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, false); } // Convert to ARGB_8888, required by tess bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true); } catch (IOException e) { Log.e(TAG, "Couldn't correct orientation: " + e.toString()); } // _image.setImageBitmap( bitmap ); setImageFilters(bitmap); } 

Then, filter the image:

public void setImageFilters(Bitmap bmpOriginal)
    {
        //Start by cropping image
        Bitmap croppedBitmap = ThumbnailUtils.extractThumbnail(bmpOriginal, 1080, 1420);

        //Then convert to grayscale
        int width, height;
        height = 1420;
        width = 1080;

        Bitmap bmpGrayscale = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        Canvas c = new Canvas(bmpGrayscale);
        Paint paint = new Paint();
        ColorMatrix cm = new ColorMatrix();
        cm.setSaturation(0);
        ColorMatrixColorFilter f = new ColorMatrixColorFilter(cm);
        paint.setColorFilter(f);
        c.drawBitmap(croppedBitmap, 0, 0, paint);

        //Finally, sharpen the image
        double weight = 11;
        double[][] sharpConfig = new double[][]
                {
                        { 0 ,   -2  , 0  },
                        { -2, weight, -2 },
                        { 0 ,   -2  , 0  }
                };

        ConvolutionMatrix convMatrix = new ConvolutionMatrix(3);
        convMatrix.applyConfig(sharpConfig);
        convMatrix.Factor = weight - 8;

        Bitmap filteredBitmap = ConvolutionMatrix.computeConvolution3x3(bmpGrayscale, convMatrix);

        //Start Optical Character Recognition
        startOCR(filteredBitmap);

        //Save filtered image
        saveFiltered(filteredBitmap);
    }

Then, saving the filtered and rotated image:

public void saveFiltered(Bitmap filteredBmp) {
        try {
            ByteArrayOutputStream bytes = new ByteArrayOutputStream();
            filteredBmp.compress(Bitmap.CompressFormat.JPEG, 20, bytes);

            //You can create a new file name "test.jpg" in sdcard folder.
            File f = new File("/sdcard/SimpleAndroidOCR/ocrgray.jpg");
            f.createNewFile();
            //Write the bytes in file
            FileOutputStream fo = new FileOutputStream(f);
            fo.write(bytes.toByteArray());

            //Remember close the FileOutput
            fo.close();

        } catch (Exception e) {
            e.printStackTrace();

        }
    }

Thanks heaps for anyone taking the time to help.

It was actually in my onPhotoTaken method. After taking and saving the screenshot in get screenshot, I am reading the file from the location it was saved to, then filtering it. I changed this line in the onPhotoTaken method:

options.inSampleSize = 4 to options.inSampleSize = 1

It does look like the jpeg compression is messing the image up. Try using a format better suited for images with sharp edges, such as of text. I would recommend png or even gif. You could also store the uncompressed BMP.

Jpeg compression works by exploiting the fact that in most pictures (nature, people, objects), sharp edges are not that visible to the human eye. This makes it really bad for storing sharp edged content, such as text.

Also, your image filter is effectively removing the anti-aliasing of the image, which further decreases the perceived image quality. That might be what you want to do, however, since it might make OCR easier.

I also missed the sampling size due to the images you uploaded being the same size here on the site. From the Android documentation :

If set to a value > 1, requests the decoder to subsample the original image, returning a smaller image to save memory. The sample size is the number of pixels in either dimension that correspond to a single pixel in the decoded bitmap. For example, inSampleSize == 4 returns an image that is 1/4 the width/height of the original, and 1/16 the number of pixels. Any value <= 1 is treated the same as 1. Note: the decoder uses a final value based on powers of 2, any other value will be rounded down to the nearest power of 2.

Setting options.inSampleSize = 4; to 1 instead will increase the quality.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM