Java OpenCV调用摄像头拍照后线程卡住、GUI冻结问题求解
我目前在进行人脸识别相关开发,尝试从网络摄像头采集照片,基于该教程改造实现拍照自动命名保存功能。相关逻辑从main()函数调用,功能本身在其他类中实现。我使用interrupt()停止线程前功能运行正常,停止后GUI中的画面冻结,程序也没有返回到GUI操作所在的类。
我的预期需求是:从摄像头采集一张照片后,用这张静态照片替换原有摄像头实时流。目前执行到采集照片、中断线程步骤后程序就会卡住。
我曾尝试将线程逻辑移入CaptureButton的ActionListener中,但执行失败,提示无法访问主GUI元素,可该元素已经定义为public,这一点我十分疑惑。
另外编译运行过程中抛出了以下警告和异常,我不清楚其具体含义:
[ WARN:0] global C:\build\master_winpack-bindings-win64-vc14-static\opencv\modules\videoio\src\cap_msmf.cpp (376) `anonymous-namespace'::SourceReaderCB::OnReadSample videoio(MSMF): OnReadSample() is called with error status: -1072873821 [ WARN:0] global C:\build\master_winpack-bindings-win64-vc14-static\opencv\modules\videoio\src\cap_msmf.cpp (388) `anonymous-namespace'::SourceReaderCB::OnReadSample videoio(MSMF): async ReadSample() call is failed with error status: -1072873821 [ WARN:1] global C:\build\master_winpack-bindings-win64-vc14-static\opencv\modules\videoio\src\cap_msmf.cpp (1021) CvCapture_MSMF::grabFrame videoio(MSMF): can't grab frame. Error: -1072873821 Exception in thread "Thread-0" CvException [org.opencv.core.CvException: cv::Exception: OpenCV(4.5.2) C:\build\master_winpack-bindings-win64-vc14-static\opencv\modules\imgcodecs\src\loadsave.cpp:896: error: (-215:Assertion failed) !image.empty() in function 'cv::imencode' ] at org.opencv.imgcodecs.Imgcodecs.imencode_1(Native Method) at org.opencv.imgcodecs.Imgcodecs.imencode(Imgcodecs.java:510) at GUI.FaceRecognition.startCamera(FaceRecognition.java:129) at main$1$1.run(main.java:19) at java.base/java.lang.Thread.run(Thread.java:834) [ WARN:2] global C:\build\master_winpack-bindings-win64-vc14-static\opencv\modules\videoio\src\cap_msmf.cpp (438) `anonymous-namespace'::SourceReaderCB::~SourceReaderCB terminating async callback Thread vorbei
我清楚自己的代码写得比较业余,猜测可以用callable()实现相关逻辑但不清楚具体用法。我暂时没法在不上传完整项目的前提下复现问题,以下是我改造后的代码:
- 功能实现函数
public boolean startCamera(){ capture= new VideoCapture(0); image=new Mat(); byte[] imageData; ImageIcon icon; String name = null; boolean echo = false; while(true){ capture.read(image); final MatOfByte buf=new MatOfByte(); Imgcodecs.imencode(".png", image,buf); imageData= buf.toArray(); icon=new ImageIcon(imageData); Feed.setIcon(icon); if(trigger==true){ name="Recognition"; Imgcodecs.imwrite("src/"+name+".png",image); trigger=false; //Auslöser zurücksetzten } File kamera=new File("src/Recognition.png"); if(kamera.exists()==true){ //capture.release(); /*try { Kamera=ImageIO.read(this.getClass().getResourceAsStream("/Recognition.png")); } catch (IOException e) { e.printStackTrace(); }*/ return echo=true; } } }
- main()函数代码
import java.awt.*; import java.io.File; import java.lang.String; import GUI.*; import org.opencv.core.Core; public class main { public static void main(String[] args){ FaceRecognition Fenster=new FaceRecognition(); System.loadLibrary(Core.NATIVE_LIBRARY_NAME); EventQueue.invokeLater(new Runnable() { @Override public void run() { //FaceRecognition Fenster=new FaceRecognition(); new Thread (new Runnable() { @Override public void run() { Fenster.startCamera(); } }).start(); //File kamera=new File(("src/Recognition.png")); } }); if(Fenster.startCamera()==true){ Thread.currentThread().interrupt(); System.out.println("Thread vorbei"); } } }
麻烦帮忙指出我哪里出错了,问题根源是什么?
补充问题
我之后参考了@pveentjer的建议重构了代码(如下),但main()中IDE提示需要将startCamera设为static,可startCamera中用到的元素不支持定义为静态,请问有什么方法可以绕过这个问题?
- 重构后的startCamera类代码
public class startCamera implements Runnable{ private CountDownLatch end; public startCamera(CountDownLatch one){ this.end=one; } @Override public void run(){ try { capture= new VideoCapture(0); image=new Mat(); byte[] imageData; ImageIcon icon; String name = null; while(true){ capture.read(image); final MatOfByte buf=new MatOfByte(); Imgcodecs.imencode(".png", image,buf); imageData= buf.toArray(); icon=new ImageIcon(imageData); Feed.setIcon(icon); if(trigger==true){ name="Recognition"; Imgcodecs.imwrite("src/"+name+".png",image); trigger=false; //Auslöser zurücksetzten } File kamera=new File("src/Recognition.png"); if(kamera.exists()==true){ capture.release(); end.countDown(); /*try { Kamera=ImageIO.read(this.getClass().getResourceAsStream("/Recognition.png")); } catch (IOException e) { e.printStackTrace(); }*/ } } } catch (Exception exception){ } }
- 重构后的main()代码
import java.awt.*; import java.io.File; import java.lang.String; import java.util.concurrent.CountDownLatch; import GUI.*; import GUI.FaceRecognition.startCamera; import org.opencv.core.Core; public class main { public static void main(String[] args){ FaceRecognition Fenster=new FaceRecognition(); CountDownLatch mark=new CountDownLatch(1); startCamera startCamera=new startCamera(mark); try { mark.await(); } catch (InterruptedException e) { e.printStackTrace(); } }
我是第一次接触多线程开发,可能犯了比较基础的错误,还请帮忙解答。
原有代码的核心错误
重复启动摄像头占用资源
你在main函数里同时调用了两次Fenster.startCamera():一次在EventQueue.invokeLater的子线程里,一次直接在主线程调用。两个线程同时抢摄像头硬件资源,导致capture.read(image)读到空帧,触发!image.empty()断言失败的异常,这也是你报错日志的直接原因。线程中断逻辑完全错误
你中断的是main主线程,而不是运行摄像头采集逻辑的子线程,所以采集线程根本不会停止,会一直占用摄像头;同时主线程中断后也不会继续执行后续逻辑,直接导致程序卡死。Swing GUI操作违反单线程规则
你在采集子线程里直接调用Feed.setIcon(icon)修改Swing组件属性,Swing不是线程安全的,非EDT线程操作GUI元素会导致界面卡顿、元素访问异常,这就是你把逻辑移到ActionListener里提示无法访问GUI元素的核心原因。摄像头资源未正确释放
你原来的代码注释掉了capture.release(),采集线程结束后没有释放摄像头硬件资源,也会导致后续操作报错。
重构后static报错的解决方法
你把startCamera定义成了FaceRecognition的内部非静态类,非静态内部类的实例必须依赖外部类FaceRecognition的实例才能创建,所以你不能直接new startCamera(mark),要改成用你创建的Fenster实例来创建,同时还要补充启动线程的代码:
startCamera startCamera = Fenster.new startCamera(mark); new Thread(startCamera).start();
另外你的重构代码还有几个问题:
- 捕获到异常后是空处理,会导致报错了你也看不到问题
- 识别到文件生成后只调用了
countDown(),没有跳出while循环,线程会一直空转 - 还是在子线程里直接操作GUI的
Feed.setIcon,需要用SwingUtilities.invokeLater包裹这部分逻辑
正确实现逻辑参考
- 只启动一个采集线程,所有GUI更新操作都放到
SwingUtilities.invokeLater里执行 - 采集到照片后先调用
capture.release()释放摄像头,再手动给Feed设置你保存的静态图片的Icon,替代实时流 - 采集线程结束不需要手动中断,触发保存条件后直接break循环即可自然结束
- 不需要用文件存在作为判断条件,你保存完图片直接就可以执行后续逻辑,不需要轮询文件
内容的提问来源于stack exchange,提问作者Quamatoc

