最近做的project需要涉及到对扫描图片的文字识别,需要将扫描得到的彩色图片变为1bit的黑白图片。
搜索到很多解决方案,基本的归纳为两个步骤,首先要将彩色图片变为8bit灰度图片(grayscale image),然后再进一步变为1bit的单色图片(1bit monochrome)。
方案一:使用ColorConvert和Dithering操作
1、代码
目前JAVA最新的图片处理包应该是JAI(Java Advanced Imaging),在JAI API的FAQ页面上,找到了下面的方法来完成这两步工作:
将彩色图片转换为8bit灰度图片(使用ColorConvert颜色转换操作) |
[java] public RenderedImage convertTo8BitGray(RenderedImage colorImage){ ParameterBlock pb = new ParameterBlock(); pb.addSource(colorImage); ColorModel cm = new ComponentColorModel( ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[]{8}, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE); pb.add(cm); RenderedImage grayImage = JAI.create(“ColorConvert”, pb); return grayImage; }[/java] |
将8bit灰度图片转换为1bit黑白图片 (使用errordiffusion或ordereddither操作) |
[java] public RenderedImage applyDithering(RenderedImage grayImage, boolean isErrorDiffusion){ // Load the ParameterBlock for the dithering operation // and set the operation name. ParameterBlock pb = new ParameterBlock(); pb.addSource(grayImage); String opName = null; if(isErrorDiffusion) { opName = “errordiffusion”; LookupTableJAI lut = new LookupTableJAI(new byte[] {(byte)0x00, (byte)0xff}); pb.add(lut); pb.add(KernelJAI.ERROR_FILTER_FLOYD_STEINBERG); } else { opName = “ordereddither”; ColorCube cube = ColorCube.createColorCube(DataBuffer.TYPE_BYTE, 0, new int[] {2}); //尝试改变2为其它值,可以得到不同效果 pb.add(cube); pb.add(KernelJAI.DITHER_MASK_441); } // Create a layout containing an IndexColorModel which maps // zero to zero and unity to 255. ImageLayout layout = new ImageLayout(); byte[] map = new byte[] {(byte)0x00, (byte)0xff}; ColorModel cm = new IndexColorModel(1, 2, map, map, map); layout.setColorModel(cm); // Create a hint containing the layout. RenderingHints hints = new RenderingHints(JAI.KEY_IMAGE_LAYOUT, layout); // Dither the image. bwImage = JAI.create(opName, pb, hints); return bwImage; } [/java] |
2、问题
这段sample code在很多地方都被提到过,我不知道别人是否能够让它顺利执行,但是在我的机器上是不行的。
问题主要出在第一个convertTo8BitGray()函数里的ColorConvert操作上。在执行完ColorConvert之后,图片的ColorModel的numberOfComponents变为1。我的理解是因为变为灰度图片,只有一个色了。
然而不知为什么,图片的SampleModel的numberOfBands却仍然保持了3,这就造成了SampleModel和ColorModel 不兼容,所以程序总是抛出java.lang.IllegalArgumentException: The specified ColorModel is incompatible with the image SampleModel…的错误。
方案二:使用重画到灰度图片上的方法
后来我在Code Beach看到了这篇文章,试用了里面的第二个方法 — 将彩色图片画到一个灰度图片上的方法,终于可以成功将彩色图片转化为8bit的灰度图片。代码如下:
[java]
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
Graphics g = image.getGraphics();
g.drawImage(colorImage, 0, 0, null);
g.dispose();
[/java]
转换的效果如下图所示:
转换前:彩色图片 |
转换后:灰度图片 |
使用Binarize将8bit灰度图片转换为1bit黑白图片
将彩色图片转换为灰度图片后,仍然可以用前面程序例子中的applyDithering()方法来将8bit灰度图片进一步转换为1bit黑白图片,得到的效果比较象报纸上的黑白图片,保留了很多灰度的层次。
为了能够更进一步得到版画效果的图片,可以使用Binarize操作。改变Binarize操作的域值(threshold)也可以得到不同效果。
代码:
[java]
/***
* Binarize image (convert image to 1 bit black and white)
* 输入图片必须为灰度图片,否则会出错。
*/
public RenderedImage applyBinarize(RenderedImage grayImage) {
// Generate a histogram.
Histogram histogram =
(Histogram)JAI.create(“histogram”, grayImage).getProperty(“histogram”);
// Get a threshold equal to the median.
double[] threshold = histogram.getPTileThreshold(0.4); //改变域值可以得到不同效果
// Binarize the image.
RenderedImage bwImage =
JAI.create(“binarize”, grayImage, new Double(threshold[0]));
return bwImage;
}//function applyBinarize
[/java]
效果:
使用Dithering的效果 | 使用Binarizing的效果 |