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():continuecamera_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 defaultcamera.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 functionsdef on_capture():home_dir = os.path.expanduser('~')cv2.imwrite(home_dir + "/capture.png", img)# Resize the image while maintaining the aspect ratiocv2image = cv2.cvtColor(img, cv2.COLOR_BGR2RGBA)current_image = Image.fromarray(cv2image)w, h = current_image.sizeratio = 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 = imgtkphoto_panel.config(image=imgtk)messagebox.showinfo("Info", "Photo taken successfully")def on_switch_res(value):global cameracamera.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_idsuccess, img = camera.read() # 从摄像头读取照片if success:cv2.waitKey(10)cv2image = cv2.cvtColor(img, cv2.COLOR_BGR2RGBA) # 转换颜色从BGR到RGBAcurrent_image = Image.fromarray(cv2image)        # 将图像转换成Image对象# 等比缩放照片w,h = current_image.sizeratio = 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 = imgtkvideo_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():continuecamera_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 defaultcamera.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_idsuccess, img = camera.read() # 从摄像头读取照片if success:cv2.waitKey(10)cv2image = cv2.cvtColor(img, cv2.COLOR_BGR2RGBA) # 转换颜色从BGR到RGBAcurrent_image = Image.fromarray(cv2image)        # 将图像转换成Image对象# 等比缩放照片w,h = current_image.sizeratio = 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 = imgtkvideo_panel.config(image=imgtk)video_loop_id = root.after(1, on_video_loop)

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HarmonyOS开发(六):构建简单页面

1、Column&Row组件 1.1、概述 一个页面由很多组件组成,如果需要把这些组件组织起来布局好,需要借助容器组件来实现。 容器组件是一种特殊的组件,它可以包含其他组件,而且按照一定的规律布局,一个容器组件中可以…

文章解读与仿真程序复现思路——中国电机工程学报EI\CSCD\北大核心《计及电动汽车需求响应的高速公路服务区光储充鲁棒优化配置》

这个标题涉及到一个关于高速公路服务区的优化配置问题,其中考虑了电动汽车需求响应和光储充的因素。让我们逐步解读这个标题: 高速公路服务区: 涉及到高速公路上的服务区,这是供驾驶员休息、加油、用餐等的地方。 电动汽车需求响…

【C语言:深入理解指针二】

文章目录 1. 二级指针2. 指针数组3. 字符指针变量4. 数组指针变量5. 二维数组传参的本质6. 函数指针变量7. 函数指针数组8. 转移表9. 回调函数10. qsort函数的使用与模拟实现 1. 二级指针 我们知道,指针变量也是变量,它也有自己的地址,使用什…

修改QtCreator/QDesigner的对象指示器高亮颜色

一、前言 QtCreator的设计中,高亮颜色太接近了,在左边点一个对象后,很难在右边对上,体验极差。 二、解决方案 创建一份style.qss,写入以下的样式: /* for QtCreator */ QDockWidget #ObjectInspector …

前端环境变量释义process.env与import.meta.env

视频教程 彻底搞懂前端环境变量使用和原理,超清楚_哔哩哔哩_bilibili 添加命令行参数 --modexxxxx 新建.env.xxxx文件,其中.env文件会在所有环境下生效 以VITE_开头,字符串无需加双引号 使用import.meta.env.VITE_xxxxx进行调用

高效聚合 | AIRIOT智慧虚拟电厂管理解决方案

传统的电力供应模式主要依靠大型发电厂和电网进行能源传输和分配,但这种模式会导致能源浪费、环境污染等问题,往往存在如下的运维问题和管理痛点: 资源整合能力差:传统电力供应模式无法集成和整合分散的电力资源,包括…

selenium下拉框的操作这样做,阿里p10都直呼牛逼

下拉框处理 web页面上经常会有下拉框,对下拉框的处理比较简单,一般分为两种情况: 一、下拉框通过元素定位识别 driver.find_element(By.XPATH,//option[value"peach"]).click() 二、创建一个select的对象,然后通过相…

若依框架参数验证

文章目录 一、前端触发参数校验异常1.前端页面2.前端代码 二、后端触发参数校验异常1.前端页面2.后端报错 三、后端自定义参数验证1.添加注解2.触发后端校验 一、前端触发参数校验异常 1.前端页面 输入不符合校验规则的值来触发 2.前端代码 校验规则数组 表单的元素 修…

[前端] V8引擎编译原理

文章目录 1.什么是V81.1 扫描器Scanner1.2 解析器parser1.3 预解析PreParser1.4 解释器Ignition1.5 编译器TurboFan 1.什么是V8 V8是谷歌的开源高性能JavaScript和WebAssembly引擎,用C编写。它被用于Chrome和Node.js等。它实现ECMAScript和WebAssembly,…

前端处理返回数据为数组对象且对象嵌套数组并重名的数据,合并名称并叠加数据

前端处理返回数据为数组对象且对象嵌套数组并重名的数据,合并名称并叠加数据 var newList[]; var table{}; var dataObj{}; var finalList[]; var tableData[{brName:营业部,dateStr:2023-11-23,tacheArr:[{dealCnt:20,tacheName:奔驰}]},{brName:营业部,dateStr:2…

Spring Boot 整合MyBatis-Plus 详解

MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。 全新的 MyBatis-Plus 3.0 版本基于 JDK8,提供了 lambda 形…

从零开始的C++(十九)

红黑树: 一种接近平衡的二叉树,平衡程度低于搜索二叉树。 特点: 1.根节点为黑 2.黑色结点的子结点可以是红色结点或黑色结点。 3.红色结点的子结点只能是黑色结点。 4.每个结点到其所有叶子结点的路径的黑色结点个数相同。 5.指向空的…