2

我正在使用 java 从扫描仪设备捕获图像。输入格式是 PGM 或 TIFF。我必须在用户界面中显示实时结果。实际上,我使用ImageJ将源输入流读取为 tiff,因为 ImageJ 还可以处理不完整的流。之后,ImagePlus对象被转换为 aBufferedImage并最终转换为 JavaFX Image

ImagePlus imagePlus = new Opener().openTiff(inputStream, "");
BufferedImage bufferedImage = imagePlus.getBufferedImage();
Image image = SwingFXUtils.toFXImage(bufferedImage, null);

这是非常缓慢的。我需要一种更快的方法来Image从 PGM 或 TIFF 流创建 JavaFX。似乎 JavaFX 实际上不支持这种格式,我也没有找到有用的库。

任何想法?

编辑#1

我决定分两步优化图像捕获。首先,在更新 ui 中的图像时,我需要更好的状态控制。这实际上已经完成并且工作正常。现在,当转换线程忙时,更新请求被丢弃。第二步是使用自行实现的 pnm 阅读器(基于建议的实现)并逐步更新我的模型中的图像......直到扫描过程完成。从设备加载图像时,这应该会减少所需的资源。我需要改变我的架构的某些部分来实现这一点。

谢谢@大家的意见。

顺便说一句:java 8 lambda 很棒:)

编辑#2

我的计划行不通,因为 JavaFX 的线程测试 :(

目前我的WritableImage后端有一个应该逐步填充数据。此图像实例设置ObjectProperty为最终绑定到ImageView. 由于WritableImage连接到 ,ImageView因此无法使用PixelWriter. 这会导致异常。

java.lang.IllegalStateException: Not on FX application thread; currentThread = pool-2-thread-1
    at com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:210) ~[jfxrt.jar:na]
    at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:393) ~[jfxrt.jar:na]
    at javafx.scene.Scene.addToDirtyList(Scene.java:529) ~[jfxrt.jar:na]
    at javafx.scene.Node.addToSceneDirtyList(Node.java:417) ~[jfxrt.jar:na]
    at javafx.scene.Node.impl_markDirty(Node.java:408) ~[jfxrt.jar:na]
    at javafx.scene.Node.transformedBoundsChanged(Node.java:3789) ~[jfxrt.jar:na]
    at javafx.scene.Node.impl_geomChanged(Node.java:3753) ~[jfxrt.jar:na]
    at javafx.scene.image.ImageView.access$700(ImageView.java:141) ~[jfxrt.jar:na]
    at javafx.scene.image.ImageView$3.invalidated(ImageView.java:285) ~[jfxrt.jar:na]
    at javafx.beans.WeakInvalidationListener.invalidated(WeakInvalidationListener.java:83) ~[jfxrt.jar:na]
    at com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:135) ~[jfxrt.jar:na]
    at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80) ~[jfxrt.jar:na]
    at javafx.beans.property.ReadOnlyObjectPropertyBase.fireValueChangedEvent(ReadOnlyObjectPropertyBase.java:74) ~[jfxrt.jar:na]
    at javafx.scene.image.Image$ObjectPropertyImpl.fireValueChangedEvent(Image.java:568) ~[jfxrt.jar:na]
    at javafx.scene.image.Image.pixelsDirty(Image.java:542) ~[jfxrt.jar:na]
    at javafx.scene.image.WritableImage$2.setArgb(WritableImage.java:170) ~[jfxrt.jar:na]
    at javafx.scene.image.WritableImage$2.setColor(WritableImage.java:179) ~[jfxrt.jar:na]

我的解决方法是创建图像的副本,但我不喜欢这种解决方案。也许可以阻止自动更改通知并手动执行此操作?

4

2 回答 2

1

下载jai_imageio.jar并将其包含在您的项目中。将 tiff 图像转换为 fx 可读图像的代码如下:

String pathToImage = "D:\\ABC.TIF";
ImageInputStream is;
try {
is = ImageIO.createImageInputStream(new File(pathToImage));  //read tiff using imageIO (JAI component)
if (is == null || is.length() == 0) {
    System.out.println("Image is null");
}

Iterator<ImageReader> iterator = ImageIO.getImageReaders(is);
if (iterator == null || !iterator.hasNext()) {
    throw new IOException("Image file format not supported by ImageIO: " + pathToImage);
}
ImageReader reader = (ImageReader) iterator.next();
reader.setInput(is);
int nbPages = reader.getNumImages(true);
BufferedImage bf = reader.read(0);   //1st page of tiff file
BufferedImage bf1 = reader.read(1);  //2nd page of tiff file
WritableImage wr = null;
WritableImage wr1 = null;
if (bf != null) {
    wr= SwingFXUtils.toFXImage(bf, null);   //convert bufferedImage (awt) into Writable Image(fx)
}
if (bf != null) {
    wr1= SwingFXUtils.toFXImage(bf1, null);  //convert bufferedImage (awt) into Writable Image(fx)
}
img_view1.setImage(wr);
img_view2.setImage(wr1);

} catch (FileNotFoundException ex) {
        Logger.getLogger(Image_WindowController.class.getName()).log(Level.SEVERE, null, ex);
} catch (IOException ex) {
        Logger.getLogger(Image_WindowController.class.getName()).log(Level.SEVERE, null, ex);
}

这是我在 Stack Overflow 上的第一个答案。希望能帮助到你!

于 2014-12-23T15:06:02.480 回答
1

作为一个实验,为了学习一些 JavaFX,我决定亲自看看实现我在上面评论中的建议有多难...... :-)

PGM 读数改编自我的 PNM ImageIO 插件,它似乎工作正常。据报道,我的 640x480 示例图像的读取时间约为 70-90 毫秒(如果有,请随时给我发送更多示例!)。

未压缩的 TIFF 应该在大致相同的时间内可读,尽管 TIFF IFD 结构的解析比非常简单的 PGM 标头更复杂。TIFF 压缩会增加一些解压缩开销,具体取决于压缩类型和设置。

import java.io.DataInputStream;
import java.io.IOException;

import javax.imageio.IIOException;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

public class PGMTest extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws IOException {
        Label root = new Label();
        Image image;

        long start = System.currentTimeMillis();
        DataInputStream input = new DataInputStream(getClass().getResourceAsStream("/house.l.pgm"));
        try {
            image = readImage(input);
        } finally {
            input.close();
        }
        System.out.printf("Read image (%f x %f) in: %d ms\n", image.getWidth(), image.getHeight(), System.currentTimeMillis() - start);

        root.setGraphic(new ImageView(image));
        Scene scene = new Scene(root);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private Image readImage(final DataInputStream input) throws IOException {
        // First parse PGM header
        PNMHeader header = PNMHeader.parse(input);

        WritableImage image = new WritableImage(header.getWidth(), header.getHeight());
        PixelWriter pixelWriter = image.getPixelWriter();

        int maxSample = header.getMaxSample(); // Needed for normalization

//        PixelFormat<ByteBuffer> gray = PixelFormat.createByteIndexedInstance(createGrayColorMap());

        byte[] rowBuffer = new byte[header.getWidth()];
        for (int y = 0; y < header.getHeight(); y++) {
            input.readFully(rowBuffer); // Read one row

//            normalize(rowBuffer, maxSample);
//            pixelWriter.setPixels(0, y, width, 1, gray, rowBuffer, 0, width); // Gives weird NPE for me...

            // As I can't get setPixels to work, we'll set pixels directly
            // Performance is probably worse than setPixels, but it seems "ok"-ish
            for (int x = 0; x < rowBuffer.length; x++) {
                int gray = (rowBuffer[x] & 0xff) * 255 / maxSample; // Normalize [0...255]
                pixelWriter.setArgb(x, y, 0xff000000 | gray << 16 | gray << 8 | gray);
            }
        }

        return image;
    }

    private int[] createGrayColorMap() {
        int[] colors = new int[256];
        for (int i = 0; i < colors.length; i++) {
            colors[i] = 0xff000000 | i << 16 | i << 8 | i;
        }
        return colors;
    }

    /**
     * Simplified version of my PNMHeader parser
     */
    private static class PNMHeader {
        public static final int PGM = 'P' << 8 | '5';

        private final int width;
        private final int height;
        private final int maxSample;

        private PNMHeader(final int width, final int height, final int maxSample) {
            this.width = width;
            this.height = height;
            this.maxSample = maxSample;
        }

        public int getWidth() {
            return width;
        }

        public int getHeight() {
            return height;
        }

        public int getMaxSample() {
            return maxSample;
        }

        public static PNMHeader parse(final DataInputStream input) throws IOException {
            short type = input.readShort();

            if (type != PGM) {
                throw new IIOException(String.format("Only PGM binay (P5) supported for now: %04x", type));
            }

            int width = 0;
            int height = 0;
            int maxSample = 0;

            while (width == 0 || height == 0 || maxSample == 0) {
                String line = input.readLine(); // For PGM I guess this is ok...

                if (line == null) {
                    throw new IIOException("Unexpeced end of stream");
                }

                if (line.indexOf('#') >= 0) {
                    // Skip comment
                    continue;
                }

                line = line.trim();

                if (!line.isEmpty()) {
                    // We have tokens...
                    String[] tokens = line.split("\\s");
                    for (String token : tokens) {
                        if (width == 0) {
                            width = Integer.parseInt(token);
                        } else if (height == 0) {
                            height = Integer.parseInt(token);
                        } else if (maxSample == 0) {
                            maxSample = Integer.parseInt(token);
                        } else {
                            throw new IIOException("Unknown PBM token: " + token);
                        }
                    }
                }
            }

            return new PNMHeader(width, height, maxSample);
        }
    }
}

我可能应该补充一点,我使用 JavaFX 2.2 在 Java 7 上编写、编译和运行了上述代码。


更新:使用PixelFormat我能够使用的预定义PixelWriter.setPixels,因此对于相同的 640x480 样本图像,读取时间进一步减少到 45-60 毫秒。这是一个新版本readImage(代码相同):

private Image readImage(final DataInputStream input) throws IOException {
    // First parse PGM header
    PNMHeader header = PNMHeader.parse(input);

    int width = header.getWidth();
    int height = header.getHeight();
    WritableImage image = new WritableImage(width, height);
    PixelWriter pixelWriter = image.getPixelWriter();

    int maxSample = header.getMaxSample(); // Needed to normalize

    PixelFormat<ByteBuffer> format = PixelFormat.getByteRgbInstance();

    byte[] rowBuffer = new byte[width * 3]; // * 3 to hold RGB 
    for (int y = 0; y < height; y++) {
        input.readFully(rowBuffer, 0, width); // Read one row

        // Expand gray to RGB triplets
        for (int i = width - 1; i > 0; i--) {
            byte gray = (byte) ((rowBuffer[i] & 0xff) * 255 / maxSample); // Normalize [0...255];
            rowBuffer[i * 3    ] = gray;
            rowBuffer[i * 3 + 1] = gray;
            rowBuffer[i * 3 + 2] = gray;
        }

        pixelWriter.setPixels(0, y, width, 1, format, rowBuffer, 0, width * 3);
    }

    return image;
}
于 2014-06-10T08:06:27.400 回答