linux上的通用拍照程序

最近因为工作需要,在ubuntu上开发了一个拍照程序。

为了找到合适的功能研究了好几种实现方式,在这里记录一下。

目录

太长不看版

探索过程

v4l2

QT

opencv4.2

打开摄像头

为什么不直接打开第一个视频节点

获取所有分辨率

切换摄像头


太长不看版

技术:python3.8+opencv4.2+tkinter

支持的功能如下:

  • 预览
  • 切换摄像头
  • 切换分辨率
  • 拍照(点击拍照之后,照片会显示在右边)

实现代码在这里:

import tkinter as tk
import cv2
from PIL import Image, ImageTk
import tkinter.messagebox as messagebox
import sys
import os

# Initialize window
root = tk.Tk()
root.title("UVC Camera")
root.geometry("1700x700")

# Detect available cameras
camera_indexes = []
for i in range(10):
    cap = cv2.VideoCapture(i)
    if not cap.isOpened():
        continue
    camera_indexes.append(i)
    cap.release()

print("Available cameras:", camera_indexes)

# Show error message if no camera is available
if len(camera_indexes) == 0:
    messagebox.showerror("Error", "Can't find the camera")
    sys.exit(0)

# Show error message if camera cannot be opened
try:
    camera = cv2.VideoCapture(camera_indexes[0])  # Open the first detected camera by default
    camera.set(6, cv2.VideoWriter_fourcc('M', 'J', 'P', 'G')) 
except:
    messagebox.showerror("Error", "The camera won't open, the equipment is damaged or the contact is bad.")
    sys.exit(0)

# Detect available resolutions
res_options = []
width = int(camera.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(camera.get(cv2.CAP_PROP_FRAME_HEIGHT))
res_options.append([width, height])

for j in range(30):
    old_width = int(camera.get(cv2.CAP_PROP_FRAME_WIDTH))
    old_height = int(camera.get(cv2.CAP_PROP_FRAME_HEIGHT))
    camera.set(cv2.CAP_PROP_FRAME_WIDTH, width+j*100)
    camera.set(cv2.CAP_PROP_FRAME_HEIGHT, height+j*100)
    new_width = int(camera.get(cv2.CAP_PROP_FRAME_WIDTH))
    new_height = int(camera.get(cv2.CAP_PROP_FRAME_HEIGHT))
    if new_width != old_width:
        res_options.append([new_width, new_height])

print("Available resolutions:", res_options)

# Set the lowest resolution as the default
camera.set(cv2.CAP_PROP_FRAME_WIDTH, res_options[0][0])
camera.set(cv2.CAP_PROP_FRAME_HEIGHT, res_options[0][1])

# Button callback functions

def on_capture():
    home_dir = os.path.expanduser('~')
    cv2.imwrite(home_dir + "/capture.png", img)
    # Resize the image while maintaining the aspect ratio
    cv2image = cv2.cvtColor(img, cv2.COLOR_BGR2RGBA)
    current_image = Image.fromarray(cv2image)
    w, h = current_image.size
    ratio = min(850.0 / w, 638.0 / h)
    current_image = current_image.resize((int(ratio * w), int(ratio * h)), Image.ANTIALIAS)
    imgtk = ImageTk.PhotoImage(image=current_image)
    photo_panel.imgtk = imgtk
    photo_panel.config(image=imgtk)
    messagebox.showinfo("Info", "Photo taken successfully")

def on_switch_res(value):
    global camera
    camera.set(cv2.CAP_PROP_FRAME_WIDTH, value[0])
    camera.set(cv2.CAP_PROP_FRAME_HEIGHT, value[1])

def on_switch_cam(value):
    global camera
    # print("切换摄像头")
    # print("选择的值是: ", str(value))
    # 结束预览
    root.after_cancel(video_loop_id)
    camera.release()
    # 创建新的捕捉对象并打开摄像头
    camera = cv2.VideoCapture(value)
    camera.set(6, cv2.VideoWriter_fourcc('M', 'J', 'P', 'G')) 
    if not camera.isOpened():
        messagebox.showerror("Error", "The camera cannot be turned on.")
        sys.exit()
    on_video_loop()
           
def on_video_loop():
    global img,video_loop_id
    success, img = camera.read() # 从摄像头读取照片
    if success:
        cv2.waitKey(10)
        cv2image = cv2.cvtColor(img, cv2.COLOR_BGR2RGBA) # 转换颜色从BGR到RGBA
        current_image = Image.fromarray(cv2image)        # 将图像转换成Image对象
        # 等比缩放照片
        w,h = current_image.size
        ratio = min(850.0/w, 600.0/h)
        current_image = current_image.resize((int(ratio * w), int(ratio * h)), Image.ANTIALIAS)
        imgtk = ImageTk.PhotoImage(image=current_image)
        video_panel.imgtk = imgtk
        video_panel.config(image=imgtk)
        video_loop_id = root.after(1, on_video_loop)
        
video_panel = tk.Label(root)
photo_panel = tk.Label(root)

video_panel.grid( # 左上居中对齐
    row=0, column=0, columnspan=4, padx=20, pady=20, sticky=tk.NW
)

photo_panel.grid( # 右上居中对齐
    row=0, column=4, columnspan=2,sticky=tk.EW, padx=20, pady=20
)

# 摄像头标签+下拉框
label3 = tk.Label(root, text="Select camera")
label3.grid(row=1, column=0, sticky="E", padx=10, pady=10)

variable1 = tk.StringVar(root)
variable1.set(camera_indexes[0])
cam_dropdown = tk.OptionMenu(root, variable1, *camera_indexes, command=on_switch_cam)
cam_dropdown.grid(row=1, column=1, sticky="W", padx=10, pady=10)

# 分辨率标签+下拉框
label4 = tk.Label(root, text="Select resolution")
label4.grid(row=1, column=2, sticky="E", padx=10, pady=10)

variable2 = tk.StringVar(root)
variable2.set(res_options[0])
res_dropdown = tk.OptionMenu(root, variable2, *res_options, command=on_switch_res)
res_dropdown.grid(row=1, column=3, sticky="W", padx=10, pady=10)

# 拍照和退出按钮
capture_button = tk.Button(root, text="Take a picture", command=on_capture)
capture_button.grid(row=1, column=4, padx=10, pady=10)

exit_button = tk.Button(root, text="Quit", command=root.quit)
exit_button.grid(row=1, column=5, padx=10, pady=10)

# 一些页面设置
root.grid_columnconfigure(0, weight=1)
root.grid_columnconfigure(1, weight=1)
root.grid_columnconfigure(2, weight=1)
root.grid_columnconfigure(3, weight=1)
root.grid_columnconfigure(4, weight=2)
root.grid_columnconfigure(5, weight=2)
root.grid_rowconfigure(0, weight=13)
root.grid_rowconfigure(1, weight=1)

on_video_loop()
root.mainloop()

探索过程

v4l2

一开始在网上找到的其实是拍照程序是v4l2的,纯c接口。

不过这个相机需要预览,v4l2接口虽然拍照正常但是没法预览,所以放弃了这套方案。

相关内容记录在:V4L2 零基础入门(一)——打开摄像头和获取摄像头基本信息_v4l2摄像头采集-CSDN博客

QT

查看资料发现QT有封装摄像头相关的接口,在qtcreator里可以直接找到。

这个demo的功能很齐全,拍照,录像都有,不过有个致命问题,高分辨率的时候预览卡的太厉害,简直卡成ppt。

opencv4.2

为了解决预览卡顿的问题,开始查找其他的方案,最终找到了Python调用opencv接口。

这套方案在高分辨率下的预览也很流畅。

实现的代码我放在一开头啦,有问题欢迎评论区。

在这边解释一些实现的细节。

打开摄像头

我这里是先打开前10个视频节点,10是为了处理同时连接多个摄像头的情况(一个摄像头有1或者2个节点)。

10这个数是随便选的,可以改成其他的数

循环前10个节点,看哪个节点能被打开,把能打开的序号存储在数组里。

最后打开数组里存储的第一个节点,并设置照片格式为mjpg。

# Detect available cameras
camera_indexes = []
for i in range(10):
    cap = cv2.VideoCapture(i)
    if not cap.isOpened():
        continue
    camera_indexes.append(i)
    cap.release()

print("Available cameras:", camera_indexes)

# Show error message if no camera is available
if len(camera_indexes) == 0:
    messagebox.showerror("Error", "Can't find the camera")
    sys.exit(0)

# Show error message if camera cannot be opened
try:
    camera = cv2.VideoCapture(camera_indexes[0])  # Open the first detected camera by default
    camera.set(6, cv2.VideoWriter_fourcc('M', 'J', 'P', 'G')) 
except:
    messagebox.showerror("Error", "The camera won't open, the equipment is damaged or the contact is bad.")
    sys.exit(0)
为什么不直接打开第一个视频节点

这里解释一下,为什么绕这么大弯,挨个找哪个节点能打开。

一般来说,直接打开第一个视频节点一般都不会有问题。

#直接打开第一个视频节点,代码会是这种形式
camera = cv2.VideoCapture(0)  

但是可能出现这样一种情况,即先连接了两个摄像头,此时视频设备的节点编号分别为1和2。

如果取下了视频设备的节点编号为1摄像头,再打开拍照程序,如果直接打开第一个节点会出现错误。

简单画的示意图如下:

获取所有分辨率

获取分辨率的流程有点复杂,先是通过CAP_PROP_FRAME_WIDTH和CAP_PROP_FRAME_HEIGHT获取最小的分辨率。

然后循环将当前已知的最大的分辨率的x和y分别+100,尝试这个分辨率在摄像头上能否设置成功。

如果设置成功,则记录改分辨率,在这个分辨率的的x和y基础上分别+100,重复这个过程。

我这里设置了循环30次,这个也是随意设置的,大家算一下能循环到摄像头的最大分辨率即可。

# Detect available resolutions
res_options = []
width = int(camera.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(camera.get(cv2.CAP_PROP_FRAME_HEIGHT))
res_options.append([width, height])

for j in range(30):
    # 前两行是获取当前分辨率
    old_width = int(camera.get(cv2.CAP_PROP_FRAME_WIDTH))
    old_height = int(camera.get(cv2.CAP_PROP_FRAME_HEIGHT))
    camera.set(cv2.CAP_PROP_FRAME_WIDTH, width+j*100)
    camera.set(cv2.CAP_PROP_FRAME_HEIGHT, height+j*100)
    new_width = int(camera.get(cv2.CAP_PROP_FRAME_WIDTH))
    new_height = int(camera.get(cv2.CAP_PROP_FRAME_HEIGHT))
    # 如果出现了新的可以设置成功的分辨率,保存下来
    if new_width != old_width:
        res_options.append([new_width, new_height])

print("Available resolutions:", res_options)

这里可能会有个问题,如果x和y分别+100的所有分辨率都不是摄像头支持的怎么办呢?

其实摄像头设置分辨率是比较智能的,不需要完全匹配。

假如支持是分辨率是950*650,实际设置分辨率1000*700,这种差的不太远的,摄像头会自动识别成自己支持的分辨率。(这只是个例子,实际差多少之内可以识别,没有详细测过)

切换摄像头

切换摄像头需要先把当前的预览停掉,释放当前的摄像头。

再重新打开摄像头,设置图片类型。

def on_switch_cam(value):
    global camera
    # print("切换摄像头")
    # print("选择的值是: ", str(value))
    # 结束预览
    root.after_cancel(video_loop_id)
    camera.release()
    # 创建新的捕捉对象并打开摄像头
    camera = cv2.VideoCapture(value)
    camera.set(6, cv2.VideoWriter_fourcc('M', 'J', 'P', 'G')) 
    if not camera.isOpened():
        messagebox.showerror("Error", "The camera cannot be turned on.")
        sys.exit()
    on_video_loop()
   
# 预览        
def on_video_loop():
    global img,video_loop_id
    success, img = camera.read() # 从摄像头读取照片
    if success:
        cv2.waitKey(10)
        cv2image = cv2.cvtColor(img, cv2.COLOR_BGR2RGBA) # 转换颜色从BGR到RGBA
        current_image = Image.fromarray(cv2image)        # 将图像转换成Image对象
        # 等比缩放照片
        w,h = current_image.size
        ratio = min(850.0/w, 600.0/h)
        current_image = current_image.resize((int(ratio * w), int(ratio * h)), Image.ANTIALIAS)
        imgtk = ImageTk.PhotoImage(image=current_image)
        video_panel.imgtk = imgtk
        video_panel.config(image=imgtk)
        video_loop_id = root.after(1, on_video_loop)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/183512.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MySQL错误之ONLY_FULL_GROUP_BY

报错信息: 翻译: 对该报错的解释 所以,实际上该报错是由于在SQL查询语句中有group by,而这个包含group by的SQL查询写的并不规范导致的,这个ONLY_FULL_GROUP_BY模式开启之后检查就会很严格,如果select列表…

51单片机IO口的四种工作状态切换

51单片机IO口的四种工作状态切换 1.概述 这篇文章介绍单片机IO引脚的四种工作模式,每个模式都有各自的用武之地,后面在驱动外设硬件时会用它不同的模式。 2.IO口四种工作模式介绍 PnM1PnM0I/O口工作模式00准双向口:灌电流达20mA&#xff…

leetcode:合并两个有序链表

题目描述 题目链接:21. 合并两个有序链表 - 力扣(LeetCode) 题目分析 这个算法思路很简单:就是直接找小尾插 定义一个tail和head,对比两个链表结点的val,小的尾插到tail->next,如果一个链表…

ebpf实战(一)-------监控udp延迟

问题背景: 为了分析udp数据通信中端到端的延迟,我们需要对整个通信链路的每个阶段进行监控,找出延迟最长的阶段. udp接收端有2个主要路径 1.数据包到达本机后,由软中断处理程序将数据包接收并放入udp socket的接收缓冲区 数据接收流程 2. 应用程序调用recvmsg等a…

如何解决tinder注册失败的问题?

tinder创立在2012年,是一款海外热门的交友软件。2020年,Tinder拥有620万用户和7500万月活跃用户。截至2021年,Tinder在全球范围内的匹配记录超过650亿。已成为全球最受欢迎的约会软件之一。 目前tinder暂时未对中国大陆开发使用,…

一个最简单的工业通讯数据分析例子

1.背景 对工业设备的通讯协议进行分析可以帮助我们更好地理解其工作原理和相关技术,并且有助于以下几个方面: 1. 优化工业设备的通讯效率:了解通讯协议的细节可以帮助我们找到通讯效率低下的原因并进行优化,提高设备的通讯效率和…

vue3实现验证码校验的功能

📓最近想实现使用vue3实现一个简易的前端验证码校验的功能,就花了点时间实现了,这只是一个简易版的,但是用在项目中是没有啥问题的,废话不多说,先来看下最终实现的效果。 📓现在让我们来一步一步…

java制作简单飞翔的鸟

创建三个包,存放代码。把图片放进文件中 APP包(运行) GameApp类 package APP; import mian.GameFrame;public class GameApp {public static void main(String[] args) {new GameFrame();} } mian包(主内容) Barri…

如何开发洗鞋店用的小程序

随着人们生活水平的提高,洗护行业是越来越细分化了,从最开始的干洗店包含洗护行业的所有服务到现在有专门为洗鞋开的店,如果开发一款洗鞋店用的小程序,可以实现用户在家下单直接有人上门取鞋的话,应该如何去开发呢&…

高质量短效SOCKS5代理IP是什么意思?作为技术你了解吗

小张是一位网络安全技术测试员,最近他接到了一个头疼的任务,那就是评估公司系统的安全性,因此他前来咨询,在得知SOCKS5代理IP可以帮他之后,他不禁产生疑问,这是什么原理?其实和小张一样的朋友不…

Lua脚本解决redis实现的分布式锁多条命令原子性问题

线程1现在持有锁之后,在执行业务逻辑过程中,他正准备删除锁,而且已经走到了条件判断的过程中,比如他已经拿到了当前这把锁确实是属于他自己的,正准备删除锁,但是此时他的锁到期了,那么此时线程2…

Flutter 小技巧之 3.16 升级最坑 M3 默认适配技巧

如果要说 Flutter 3.16 升级里是最坑的是什么?那我肯定要说是 Material 3 default (M3)。 倒不是说 M3 bug 多,也不是 M3 在 3.16 上使用起来多麻烦,因为虽然从 3.16 开始,MaterialApp 里的 useMaterial3 …

Shell循环:for(一)

语法结构: for 变量名 [ in 取值列表] do 循环体 done 示例1: 1、需求:自动循环创建10个用户 2、演示: [rootlocalhost ~]# vim for.sh #脚本编写 #!/bin/bash for i in {1..10} do useradd "user$…

探究Kafka原理-2.Kafka基本命令实操

👏作者简介:大家好,我是爱吃芝士的土豆倪,24届校招生Java选手,很高兴认识大家📕系列专栏:Spring源码、JUC源码、Kafka原理🔥如果感觉博主的文章还不错的话,请&#x1f44…

csv文件添加文件内容和读取

append content to file import numpy as np acc_listnp.array([0.97,0.92,0.93,0.89]) # 注意这个地方添加文件不需要特别声明是什么文件 file open("result.csv", "a") print("{:.2f}, {:.2f}".format(acc_list.mean(), acc_list.std()), f…

服务器安全如何保障

主机安全是指保护计算机主机(也称为服务器、终端或主机设备)免受潜在的安全威胁和攻击的一系列措施和实践。主机安全旨在防止未经授权的访问、数据泄露、恶意软件感染和其他安全漏洞的利用,主机一旦被黑客入侵,企业会面临很多安全…

buck降压电路

一、Buck电路的拓扑结构 Buck是直流转直流的降压电路,下面是拓扑结构,作为硬件工程师,这个最好是能够记下来,了然于胸。 为啥要记下来,自然是因为这个电路太基础了,并且谁都会用到,更重要的一点,面试可能会考。。。 上图是个异步buck,同步buck就是将里面的二极管换成M…

手持式无线通信频谱分析仪 MS2713E

MS2713E 手持式无线通信频谱分析仪 安立手持式无线通信频谱分析仪 MS2713E 旨在处理最恶劣的现场条件,使您能够监控、定位、识别和分析各种蜂窝、2G/3G/4G、陆地移动无线电、Wi-Fi 和广播信号。多功能 Spectrum Master 在定位和识别宽频率范围内的信号时&#xff0…

Java设计模式系列:单例设计模式

Java设计模式系列:单例设计模式 介绍 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法) 比如 Hiberna…

【云原生 Prometheus篇】Prometheus的动态服务发现机制与认证配置

目录 一、Prometheus服务发现的方式1.1 基于文件的服务发现1.2 基于consul的服务发现1.3 基于 Kubernetes API 的服务发现1.3.1 简介1.3.2 基于Kurbernetes发现机制的部分配置参数 二、实例一:部署基于文件的服务发现2.1 创建用于服务发现的文件2.2 修改Prometheus的…