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

OpenCV Python如何从同一摄像头取流同时实现Tkinter展示与QR码解码

问题描述

我开发的程序需要通过tkinter展示树莓派摄像头的画面,同时对进入画面的QR码进行解码,运行时持续报错:TypeError: cannot unpack non-iterable NoneType object,经排查是解码函数无法访问摄像头流导致。我已尝试将frame设为全局变量,但问题未得到解决。

原始代码
# import all the necessary modules
from tkinter import *
from multiprocessing import Process, Queue
from queue import Empty  # for excepting a specific error

import cv2
import pyzbar.pyzbar as pyzbar
import sys
import os
import requests
from PIL import Image, ImageTk
import time

def resetGUI():
   root.configure(bg='white')
   nameFrame.configure(bg='white')
   instructionsFrame.configure(bg='white')
   nameLabel.config(text="", bg='white')
   instructionsLabel.config(text="Place your QR code under the scanner below.", bg='white')

def processScan(code):
   if code != "":
         print (code)
         #some post request
         return ["hellllllo"]

def showVideo():
   _, frame = vid.read()
   frame = cv2.flip(frame, 1)
   cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
   img = Image.fromarray(cv2image)
   imgtk = ImageTk.PhotoImage(image=img)
   lmain.imgtk = imgtk
   lmain.configure(image=imgtk)
   cv2.waitKey(5)
   cv2.destroyAllWindows
   lmain.after(10, showVideo)

# this is the function that will be run in a child process
def startScanning(queue_):
   i = 0
   while i<2:
      # _,f = vid.read()
       _,frame = vid.read()
       decoded = pyzbar.decode(frame)
       for obj in decoded:
           queue_.put(obj.data)
           print('decoded')
       #cv2.imshow('QRCode',f)
       #cv2.waitKey(5)
       #cv2.destroyAllWindows
       #time.sleep(1)


# just a function to not write a `lambda`, just easier
# to read code
def start_process():
    Process(target=startScanning, args=(queue, ), daemon=True).start()


def updateGUI():
   try:
      data = queue.get(block=False)
      while not queue.empty():
         queue.get()
   except Empty:
      pass
   else:
      #queue.clear()
      code = processScan(data)
      if code:
         if len(code) > 1: #Pass ACCEPTED
            root.configure(bg='green'): #Pass DENIED
         root.after(3000, resetGUI)
   finally:
      root.after(100, updateGUI)


# crucial part is to use this if statement because
# child processes run this whole script again
if __name__ == '__main__':
   root = Tk()

   logo     = "logo.png"
   settings = "settings.png"

   if os.environ.get('DISPLAY','') == '':
     print('no display found. Using :0.0')
     os.environ.__setitem__('DISPLAY', ':0.0')


   #create main window
   #root.attributes("-fullscreen", True)
   root.title("Title")
   root.geometry("480x800")
   root.configure(bg='white')
   ttelogo = PhotoImage(file = logo)
   settingslogo = PhotoImage(file = settings)

   #settings button
   settingsFrame = Frame(root,width=50,height=50,bg="white")
   settingsFrame.pack_propagate(0) # Stops child widgets of label_frame from resizing it
   settingsBtn = Button(settingsFrame, image=settingslogo).pack()
   settingsFrame.place(x=430,y=0)

   #logo
   img = Label(root, image=ttelogo, bg='white')
   img.image = ttelogo
   img.place(x=176.5,y=10)

   #Name Label
   nameFrame = Frame(root,width=400,height=100,bg="white")
   nameFrame.pack_propagate(0) # Stops child widgets of label_frame from resizing it
   nameLabel = Label(nameFrame,bg="white",fg="black",text="",font=("Calibri",22))
   nameLabel.pack()
   nameFrame.place(x=40,y=140)

   #Instructions Label
   instructionsFrame = Frame(root,width=440,height=100,bg="white")
   instructionsFrame.pack_propagate(0) # Stops child widgets of label_frame from resizing it
   instructionsLabel = Label(instructionsFrame,bg="white",fg="black",text="Hello",font=("Calibri",15))
   instructionsLabel.pack()
   instructionsFrame.place(x=20,y=210)

   #Camera Window
   cameraFrame = Frame(root, width=440, height=480)
   cameraFrame.place(x=20, y=260)

   #Camera Feed
   lmain = Label(cameraFrame)
   lmain.place(x=0, y=0)

   vid = cv2.VideoCapture(0)

   # define queue (since it is a global variable now
   # it can be easily used in the functions)
   queue = Queue()

   #label = Label(root)
   #label.pack()

   # initially start the update function
   updateGUI()

   # just a button for starting the process, but you can also simply
   # call the function
   start_process()

   showVideo()
   root.mainloop()

迭代版本1:改用Threading并移除冗余代码

# import all the necessary modules
from tkinter import *
from multiprocessing import Process, Queue
from queue import Empty  # for excepting a specific error

import cv2
import pyzbar.pyzbar as pyzbar
import sys
import os
import requests
from PIL import Image, ImageTk
import time
import threading

def resetGUI():
   root.configure(bg='white')
   nameFrame.configure(bg='white')
   instructionsFrame.configure(bg='white')
   nameLabel.config(text="", bg='white')
   instructionsLabel.config(text="Place your QR code under the scanner below.", bg='white')

def processScan(code):
   if code != "":
         print (code)
         #some post request
         return ["hellllllo"]

def showVideo():
   vid = cv2.VideoCapture(0)
   _, frame = vid.read()
   frame = cv2.flip(frame, 1)
   cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
   img = Image.fromarray(cv2image)
   imgtk = ImageTk.PhotoImage(image=img)
   lmain.imgtk = imgtk
   lmain.configure(image=imgtk)
   cv2.waitKey(5)
   cv2.destroyAllWindows
   lmain.after(10, showVideo)

# this is the function that will be run in a child process
def startScanning(queue_):
   i = 0
   while i<2:
      vid = cv2.VideoCapture(0)
      # _,f = vid.read()
       _,frame = vid.read()
       decoded = pyzbar.decode(frame)
       for obj in decoded:
           queue_.put(obj.data)
           print('decoded')

def start_process():
    threading.Thread(target=startScanning, args=(queue, ), daemon=True).start()


def updateGUI():
   try:
      data = queue.get(block=False)
      while not queue.empty():
         queue.get()
   except Empty:
      pass
   else:
      #queue.clear()
      code = processScan(data)
      if code:
         if len(code) > 1: #Pass ACCEPTED
            root.configure(bg='green'): #Pass DENIED
         root.after(3000, resetGUI)
   finally:
      root.after(100, updateGUI)

if __name__ == '__main__':
   root = Tk()

   logo     = "logo.png"
   settings = "settings.png"

   if os.environ.get('DISPLAY','') == '':
     print('no display found. Using :0.0')
     os.environ.__setitem__('DISPLAY', ':0.0')

   #create main window
   #root.attributes("-fullscreen", True)
   root.title("Title")
   root.geometry("480x800")
   root.configure(bg='white')

   #Camera Window
   cameraFrame = Frame(root, width=440, height=480)
   cameraFrame.place(x=20, y=260)

   #Camera Feed
   lmain = Label(cameraFrame)
   lmain.place(x=0, y=0)

   queue = Queue()

   # initially start the update function
   updateGUI()

   start_process()

   showVideo()
   root.mainloop()

迭代版本2:尝试将vid设为全局变量

from tkinter import *
from multiprocessing import Process, Queue
from queue import Empty  # for excepting a specific error

import cv2
import pyzbar.pyzbar as pyzbar
import sys
import os
import requests
from PIL import Image, ImageTk
import time
import threading

def resetGUI():
   root.configure(bg='white')
   nameFrame.configure(bg='white')
   instructionsFrame.configure(bg='white')
   nameLabel.config(text="", bg='white')
   instructionsLabel.config(text="Place your QR code under the scanner below.", bg='white')

def processScan(code):
   if code != "":
         print (code)
         #some post request
         return ["hellllllo"]

def showVideo():
   global vid
   vid = cv2.VideoCapture(0)
   _, frame = vid.read()
   frame = cv2.flip(frame, 1)
   cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
   img = Image.fromarray(cv2image)
   imgtk = ImageTk.PhotoImage(image=img)
   lmain.imgtk = imgtk
   lmain.configure(image=imgtk)
   cv2.waitKey(5)
   cv2.destroyAllWindows
   lmain.after(10, showVideo)

# this is the function that will be run in a child process
def startScanning(queue_):
   i = 0
   while i<2:
      # _,f = vid.read()
       _,frame = vid.read()
       decoded = pyzbar.decode(frame)
       for obj in decoded:
           queue_.put(obj.data)
           print('decoded')

def start_process():
    threading.Thread(target=startScanning, args=(queue, ), daemon=True).start()


def updateGUI():
   try:
      data = queue.get(block=False)
      while not queue.empty():
         queue.get()
   except Empty:
      pass
   else:
      #queue.clear()
      code = processScan(data)
      if code:
         if len(code) > 1: #Pass ACCEPTED
            root.configure(bg='green'): #Pass DENIED
         root.after(3000, resetGUI)
   finally:
      root.after(100, updateGUI)

if __name__ == '__main__':
   root = Tk()

   logo     = "logo.png"
   settings = "settings.png"

   if os.environ.get('DISPLAY','') == '':
     print('no display found. Using :0.0')
     os.environ.__setitem__('DISPLAY', ':0.0')

   #create main window
   #root.attributes("-fullscreen", True)
   root.title("Title")
   root.geometry("480x800")
   root.configure(bg='white')

   #Camera Window
   cameraFrame = Frame(root, width=440, height=480)
   cameraFrame.place(x=20, y=260)

   #Camera Feed
   lmain = Label(cameraFrame)
   lmain.place(x=0, y=0)

   queue = Queue()

   # initially start the update function
   updateGUI()

   showVideo()

   start_process()

   root.mainloop()

最终报错信息

[ WARN:0] global /root/opencv/modules/videoio/src/cap_v4l.cpp (890) open VIDEOIO(V4L2:/dev/video0): can't open camera by index
Exception in Tkinter callback
Exception in thread Thread-1:
Traceback (most recent call last):
  File "/usr/lib/python3.7/threading.py", line 917, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.7/threading.py", line 865, in run
    self._target(*self._args, **self._kwargs)
  File "stackoverflow2.py", line 49, in startScanning
    decoded = pyzbar.decode(frame)
  File "/usr/local/lib/python3.7/dist-packages/pyzbar/pyzbar.py", line 181, in decode
    pixels, width, height = _pixel_data(image)
  File "/usr/local/lib/python3.7/dist-packages/pyzbar/pyzbar.py", line 147, in _pixel_data
    pixels, width, height = image
TypeError: cannot unpack non-iterable NoneType object

Traceback (most recent call last):
  File "/usr/lib/python3.7/tkinter/__init__.py", line 1705, in __call__
    return self.func(*args)
  File "/usr/lib/python3.7/tkinter/__init__.py", line 749, in callit
    func(*args)
  File "stackoverflow2.py", line 34, in showVideo
    cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
cv2.error: OpenCV(4.5.3) /root/opencv/modules/imgproc/src/color.cpp:182: error: (-215:Assertion failed) !_src.empty() in function 'cvtColor'
解决方案

核心问题原因

  • Linux下V4L2协议的摄像头同一时间只能被一个进程/线程独占,你在多个位置重复调用cv2.VideoCapture(0)会导致后续打开请求直接失败,vid.read()会返回(False, None),空的frame传入后续处理逻辑就会触发你遇到的两类报错。
  • 你在showVideo函数的循环里每次都重新初始化摄像头,会导致摄像头持续被抢占,根本无法正常读取帧。
  • 缺少帧读取成功校验,拿到空frame后直接做处理,没有做异常拦截。

修复步骤

  1. 全局仅初始化一次摄像头实例,放在主程序入口处,不要在循环或者子线程里重复打开。
  2. 所有逻辑共用同一个摄像头实例,扫码线程不需要单独读帧,直接用画面展示逻辑已经读取到的全局frame即可,避免冲突。
  3. 每次调用vid.read()后先判断返回的第一个值是否为True,确认帧读取成功后再执行后续操作。
  4. 不要在子进程里操作摄像头,改用线程的情况下可以正常共享同一个摄像头实例。

修复后核心代码示例

from tkinter import *
from multiprocessing import Queue
from queue import Empty
import cv2
import pyzbar.pyzbar as pyzbar
import os
from PIL import Image, ImageTk
import threading

# 全局变量存储当前帧
current_frame = None

def resetGUI():
   root.configure(bg='white')
   nameFrame.configure(bg='white')
   instructionsFrame.configure(bg='white')
   nameLabel.config(text="", bg='white')
   instructionsLabel.config(text="Place your QR code under the scanner below.", bg='white')

def processScan(code):
   if code != "":
         print(code)
         # 你的post请求逻辑
         return ["hellllllo"]

def showVideo():
   global current_frame
   ret, frame = vid.read()
   if not ret:
       lmain.after(10, showVideo)
       return
   current_frame = frame.copy()
   frame = cv2.flip(frame, 1)
   cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
   img = Image.fromarray(cv2image)
   imgtk = ImageTk.PhotoImage(image=img)
   lmain.imgtk = imgtk
   lmain.configure(image=imgtk)
   lmain.after(10, showVideo)

def startScanning(queue_):
   while True:
       if current_frame is None:
           continue
       decoded = pyzbar.decode(current_frame)
       for obj in decoded:
           queue_.put(obj.data)
           print('decoded')

def start_process():
    threading.Thread(target=startScanning, args=(queue, ), daemon=True).start()

def updateGUI():
   try:
      data = queue.get(block=False)
      while not queue.empty():
         queue.get()
   except Empty:
      pass
   else:
      code = processScan(data)
相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 23:30:00