我正在编写一个程序来连续处理和显示来自Java中OpenCV
VideoCapture
对象的视频流。我已经设置了一个带有控件的JFrame
来选择合适的视频源,然后单击一个按钮开始处理来自源的图像,并通过连续循环在“ImagePanel”(参见下面的代码)中显示结果。
我遇到的第一个问题是按下按钮的ActionListener
是“冻结”JFrame
。我发现,如果我将视频源直接硬编码到代码中并绕过按钮,请按预期按VideoCapture
上显示的ImagePanel
图像。我发现“按钮冻结”问题的解决方案是在一个单独的线程中执行按钮动作监听器中的函数调用。
现在JFrame
不再冻结,但 ImagePanel
不再显示我正在从VideoCapture
对象中读取的图像。为什么?
我假设这可能是因为代码在单独的线程上执行,因此UI无法正确更新,但我不熟悉此上下文中的线程。这是我的第一个stackoverflow问题。通常情况下,我能够找到一个已经发布在我感兴趣的主题上的答案,但在这个问题上没有运气。请指教。
以下是我的问题的快速简化。我尽可能地减少了提供基本类文件来重现我的问题。
ImagePanel.java - 用于显示缓冲图像的自定义JPanel
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import javax.swing.JPanel;
import org.opencv.core.Mat;
public class ImagePanel extends JPanel {
/**
*
*/
private static final long serialVersionUID = -7065581626827263107L;
private BufferedImage image;
public BufferedImage getImage(){
return image;
}
public void setImage(BufferedImage newImage){
image=newImage;
}
public BufferedImage matToBufferedImage(Mat matrix){
int cols = matrix.cols();
int rows = matrix.rows();
int elemSize = (int)matrix.elemSize();
byte[] data = new byte[cols * rows * elemSize];
int type;
matrix.get(0, 0, data);
switch (matrix.channels()) {
case 1:
type = BufferedImage.TYPE_BYTE_GRAY;
break;
case 3:
type = BufferedImage.TYPE_3BYTE_BGR;
//BGR to RGB
byte b;
//Swap blue and red bytes
for(int i=0; i<data.length; i=i+3) {
b = data[i];
data[i] = data[i+2];
data[i+2] = b;
}
break;
default:
return null;
}
//Create the new buffered image from the byte array data
BufferedImage image2 = new BufferedImage(cols, rows, type);
image2.getRaster().setDataElements(0, 0, cols, rows, data);
return image2;
}
@Override
protected void paintComponent(Graphics g){
super.paintComponent(g);
BufferedImage temp = this.getImage();
//Draw the buffered image
if(temp != null)
g.drawImage(temp,0,0,640,480, this); //temp.getWidth() temp.getHeight()
}
}
TestPanel.java - 保存UI控件和ImagePanel以显示图像
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JTextField;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.opencv.highgui.VideoCapture;
import org.opencv.imgproc.Imgproc;
public class TestPanel extends JPanel {
//Selects the video input device for OpenCV
private static final int VIDEO_INPUT_DEVICE = 1;
public static final int PANE_HEIGHT = 700;
public static final int PANE_WIDTH = 710;
private static final long serialVersionUID = 1L;
private JTextField ipField, usernameField;
private JPasswordField passwordField;
private JLabel feedbackLabel;
private JPanel controlsPanel, finishedPanel;
private ImagePanel imagePanel;
private JButton captureButton, finishedButton;
// Constructor
public TestPanel(){
super();
this.setSize(710, 600);
controlsPanel = new JPanel();
controlsPanel.setLayout(new FlowLayout(FlowLayout.CENTER));
finishedPanel = new JPanel();
finishedPanel.setLayout(new FlowLayout(FlowLayout.CENTER));
imagePanel = new ImagePanel();
imagePanel.setSize(640, 480);
captureButton = new JButton("Fetch Video Feed");
setCaptureButtonListener();
finishedButton = new JButton("Finished Calibration");
ipField = new JTextField("Enter Camera IP");
usernameField = new JTextField("Enter Username");
passwordField = new JPasswordField("Enter Password");
feedbackLabel = new JLabel();
// Add Controls
controlsPanel.add(captureButton);
controlsPanel.add(ipField);
controlsPanel.add(usernameField);
controlsPanel.add(passwordField);
controlsPanel.add(feedbackLabel);
finishedPanel.add(finishedButton);
this.setLayout(new BorderLayout());
this.setBorder(BorderFactory.createEmptyBorder(30, 10, 10, 10));
this.add(controlsPanel, BorderLayout.NORTH);
this.add(Box.createHorizontalStrut(15), BorderLayout.WEST);
this.add(imagePanel, BorderLayout.CENTER);
this.add(finishedPanel, BorderLayout.SOUTH);
}
public void setImageWithMat(Mat newimage){
imagePanel.setImage(this.matToBufferedImage(newimage));
return;
}
/**
* Converts/writes a Mat into a BufferedImage.
*
* @param matrix Mat of type CV_8UC3 or CV_8UC1
* @return BufferedImage of type TYPE_3BYTE_BGR or TYPE_BYTE_GRAY
*/
public BufferedImage matToBufferedImage(Mat matrix) {
return imagePanel.matToBufferedImage(matrix);
}
/**
* Set action listener for capture button.
*/
private void setCaptureButtonListener(){
captureButton.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent e) {
new Thread(new Runnable(){
@Override
public void run() {
runSingleCalibration();
}
});
}
});
}
/**
* Runs calibration for one video input stream.
*/
private void runSingleCalibration(){
this.setVisible(true);
// Load the native library.
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
//Get video input
VideoCapture capture = new VideoCapture(VIDEO_INPUT_DEVICE);
Mat image = new Mat();
while(capture.read(image)){
// Apply the GaussianBlur
Imgproc.GaussianBlur(image, image, new Size(19,19),0,0);
//Output the images to their respective frames
this.setImageWithMat(image);
this.repaint();
}
//Release the capture device
capture.release();
}
}
TestCV.java - 用于保存TestPanel的简单JFrame
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class TestCV {
private static JFrame frame;
public static void createAndShowGUI() {
//Jframe.setDefaultLookAndFeelDecorated(true);
if(frame == null){
frame = new JFrame("RC Tracking");
}
frame.setContentPane(new TestPanel());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(710, 710);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args){
//Schedule a job for the event-dispatching thread:
//creating and showing frame application's GUI.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}