You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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();
    }
}

我是第一次接触多线程开发,可能犯了比较基础的错误,还请帮忙解答。


问题原因与解决方案

原有代码的核心错误

  1. 重复启动摄像头占用资源
    你在main函数里同时调用了两次Fenster.startCamera():一次在EventQueue.invokeLater的子线程里,一次直接在主线程调用。两个线程同时抢摄像头硬件资源,导致capture.read(image)读到空帧,触发!image.empty()断言失败的异常,这也是你报错日志的直接原因。

  2. 线程中断逻辑完全错误
    你中断的是main主线程,而不是运行摄像头采集逻辑的子线程,所以采集线程根本不会停止,会一直占用摄像头;同时主线程中断后也不会继续执行后续逻辑,直接导致程序卡死。

  3. Swing GUI操作违反单线程规则
    你在采集子线程里直接调用Feed.setIcon(icon)修改Swing组件属性,Swing不是线程安全的,非EDT线程操作GUI元素会导致界面卡顿、元素访问异常,这就是你把逻辑移到ActionListener里提示无法访问GUI元素的核心原因。

  4. 摄像头资源未正确释放
    你原来的代码注释掉了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包裹这部分逻辑

正确实现逻辑参考

  1. 只启动一个采集线程,所有GUI更新操作都放到SwingUtilities.invokeLater里执行
  2. 采集到照片后先调用capture.release()释放摄像头,再手动给Feed设置你保存的静态图片的Icon,替代实时流
  3. 采集线程结束不需要手动中断,触发保存条件后直接break循环即可自然结束
  4. 不需要用文件存在作为判断条件,你保存完图片直接就可以执行后续逻辑,不需要轮询文件

内容的提问来源于stack exchange,提问作者Quamatoc

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.03 15:45:03