当前位置: 首页 > news >正文

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,这种差的不太远的,摄像头会自动识别成自己支持的分辨率950*650。(这只是个例子,实际差多少之内可以识别,没有详细测过)

切换摄像头

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

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

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)

相关文章:

linux上的通用拍照程序

最近因为工作需要,在ubuntu上开发了一个拍照程序。 为了找到合适的功能研究了好几种实现方式,在这里记录一下。 目录 太长不看版 探索过程 v4l2 QT opencv4.2 打开摄像头 为什么不直接打开第一个视频节点 获取所有分辨率 切换摄像头 太长不看…...

代码随想录-刷题第七天

454. 四数相加II 题目链接:454. 四数相加II 思路:哈希法。使用map集合,key存放ab的值,value存放ab出现的次数。使用两层循环,循环前两个数组,找出ab,对map赋值。再用两层循环,遍历…...

C# 获取图像、字体等对象大小的数据结构SizeF

如果你想要获取字符串 "你好吗" 的字节数组长度或者字符数, 使用如下代码: string s "你好吗"; //字节数组长度 int byteCount System.Text.Encoding.UTF8.GetBytes(s).Length; //字符数 int charCount s.Length; 如果你想获取…...

「 系统设计 」 为什么要做架构分层?

「 系统设计 」 为什么要做架构分层? 参考&鸣谢 3.设计模式之分层思维:为什么要做代码分层架构? 从零开始学架构(八)分层架构和设计模式 架构模式之分层架构总结 文章目录 「 系统设计 」 为什么要做架构分层&…...

4:kotlin 方法(Functions)

想要声明一个函数需要使用fun关键字 fun hello() {return println("Hello, world!") }fun main() {hello()// Hello, world! }格式: fun 方法名(参数1: 参数1类型, 参数2 : 参数2类型, ...): 返回值类型 {方法体return 返回值 }fun 方法名(参数1: 参数1类型, 参数2…...

Pycharm run 输出界面控制一行能够输出的元素个数

Pycharm run 输出界面控制一行能够输出的元素个数 今天遇到了一个问题,当我们在 Pycharm 中打印输出数组时,如果数组一行的元素个数过多,那么我们在打印时就会出现以下问题。 代码如下: import numpy as npx np.array([[0., 0.7…...

C++初级项目webserver项目流程介绍(2)

一、引言 C的webserver项目是自己在学完网络编程后根据网课的内容做的一个初级的网络编程项目。 这个项目的效果是可以在浏览器通过输入网络IP地址和端口,然后打开对应的文件目录 效果如下: 也可以打开文件夹后点击目录,打开到对应的文件夹…...

SIPp mac和debian用法可能略有差别

<ereg regexp"<(.*)>" search_in"hdr" header"Contact:" check_it"true" assign_to"dummy,remote_contact"/> debian没事&#xff0c;但mac报错 <变&lt >变&gt 就都冇问题了 https://github.…...

echarts的横向柱状图文字省略,鼠标移入显示内容 vue3

效果图 文字省略 提示 如果是在x轴上的&#xff0c;就在x轴上添加triggerEvent: true,如果是y轴就在y轴添加&#xff0c;我是在y轴上添加的 并且自定义的方法&#xff08;我取名为extension&#xff09; // echarts 横向省略文字 鼠标移入显示内容 export const extension…...

laravel8安装多应用多模块(笔记三)

先安装laravel8 Laravel 安装&#xff08;笔记一&#xff09;-CSDN博客 一、进入项目根目录安装 laravel-modules composer require nwidart/laravel-modules 二、 大于laravel5需配置provider&#xff0c;自动生成配置文件 php artisan vendor:publish --provider"Nwid…...

Vue组件的几种通信方式

这里写目录标题 Vue组件的几种通信&#xff08;数据传递&#xff09;方式非父子组件间通信&#xff08;Bus事件总线&#xff09;介绍实例 非父子通信-provide&inject1.作用2.场景3.语法4.注意 父子组件间的通信固定props属性名&#xff08;v-model&#xff09;介绍实例 不固…...

golang panic关键词执行原理与代码分析

使用的go版本为 go1.21.2 首先我们写一个简单的panic调度与捕获代码 package mainfunc main() {defer func() {recover()}()panic("panic test") }通过go build -gcflags -S main.go获取到对应的汇编代码 可以看到当我们调度panic时&#xff0c;Go的编译器会将这段…...

Error running Tomcat8: Address localhost:1099 is already in use 错误解决

摘要: 有时候运行web项目的时候会遇到 Error running Tomcat8: Address localhost:1099 is already in use 的错误&#xff0c;导致web项目无法运行。这篇 blog 介绍了解决办法。 有时候运行web项目的时候会遇到 Error running Tomcat8: Address localhost:1099 is already in …...

android studio如何给安卓虚拟机发送短信

首先&#xff0c;cd到指定路径 默认情况下&#xff0c;Android SDK通常安装在以下位置&#xff1a; Windows&#xff1a;C:\Users\YourUsername\AppData\Local\Android\Sdk\platform-toolsmacOS&#xff1a;/Users/YourUsername/Library/Android/sdk/platform-toolsLinux&…...

立体仓库PLC控制系统子站诊断功能块

// //获取profinet网络已组态站信息 // //MODE:0自动辨识是获取组态信息还是错误信息 //MODE:1获取IO 设备从站已组态 //MODE:2获取IO 设备 从站故障 //MODE:3获取IO 设备 从站已禁用 //MODE:4获取IO 设备 从站存在 //MODE:5获取IO 设备 从站出现问题 // //站点状态字节位含义 …...

NFT Insider115:The Sandbox开设元宇宙Diorama快闪店,​YGG Web3 游戏峰会已开幕

引言&#xff1a;NFT Insider由NFT收藏组织WHALE Members、BeepCrypto联合出品&#xff0c;浓缩每周NFT新闻&#xff0c;为大家带来关于NFT最全面、最新鲜、最有价值的讯息。每期周报将从NFT市场数据&#xff0c;艺术新闻类&#xff0c;游戏新闻类&#xff0c;虚拟世界类&#…...

【Redis篇】简述Java中操作Redis的方法

文章目录 &#x1f384;简述Jedis&#x1f384;Jedis优点&#x1f354;使用Jedis连接Redis⭐进行测试&#x1f388;进行测试 Redis&#xff08;Remote Dictionary Server&#xff09;是一种流行的高性能内存数据库&#xff0c;广泛应用于各种应用程序和系统中。作为Java开发人员…...

深度解读英伟达新一轮对华特供芯片H20、L20、L2的定位

大家好&#xff0c;我是极智视界&#xff0c;欢迎关注我的公众号&#xff0c;获取我的更多前沿科技分享 邀您加入我的知识星球「极智视界」&#xff0c;星球内有超多好玩的项目实战源码和资源下载&#xff0c;链接&#xff1a;https://t.zsxq.com/0aiNxERDq 因为一直从事 AI 工…...

一起学docker系列之九docker运行mysql 碰到的各种坑及解决方法

目录 前言1 Docker 运行mysql命令2 坑一&#xff1a;无法读取/etc/mysql/conf.d目录的问题3 坑二&#xff1a;/tmp/ibnr0mis 文件无法创建/写入的问题4 坑三&#xff1a;Navicat 连接错误&#xff08;1045-access denied&#xff09;5 坑四&#xff1a;MySQL 登录失败问题结语 …...

利用Nginx与php处理方式不同绕过Nginx_host实现SQL注入

目录 首先需要搭建环境 nginxphpmysql环境&#xff1a; 搭建网站 FILTER_VALIDATE_EMAIL 绕过 方法1&#xff1a;冒号号分割host字段 方法2&#xff1a;冒号号分割host字段 方法3&#xff1a;SNI扩展绕过 首先需要搭建环境 nginxphpmysql环境&#xff1a; php安装包&a…...

5分钟搞定中科蓝讯SDK编译:用CodeBlocks快速验证RV32-Toolchain环境配置

5分钟搞定中科蓝讯SDK编译&#xff1a;用CodeBlocks快速验证RV32-Toolchain环境配置 对于嵌入式开发者来说&#xff0c;搭建一个稳定可靠的开发环境往往是项目开发的第一步。中科蓝讯基于RISC-V架构的蓝牙芯片方案&#xff0c;以其高性价比和低功耗特性&#xff0c;在TWS耳机、…...

Vue 3项目里给组件起名index.vue就报错?别慌,这四种处理ESLint规则的方法总有一个适合你

Vue 3项目中index.vue组件命名报错的深度解决方案指南 刚接触Vue 3的开发者经常会遇到一个看似简单却令人困惑的问题&#xff1a;当你在项目中创建一个名为index.vue的组件时&#xff0c;ESLint会立即抛出错误提示"Component name index should always be multi-word"…...

Windows系统优化架构设计:Win11Debloat模块化去冗余技术实现

Windows系统优化架构设计&#xff1a;Win11Debloat模块化去冗余技术实现 【免费下载链接】Win11Debloat A simple, lightweight PowerShell script that allows you to remove pre-installed apps, disable telemetry, as well as perform various other changes to declutter …...

2026 年降噪蓝牙耳机推荐|8 款硬核实测 降噪 / 续航 / 延迟全维度横评

日常通勤被噪音打扰、办公需要专注、游戏想要低延迟体验&#xff0c;一款好用的降噪蓝牙耳机成为刚需。这篇2026 年降噪蓝牙耳机推荐&#xff0c;精选 6款市面热门机型&#xff0c;逐一测评&#xff0c;用直白的参数解读和场景化说明&#xff0c;帮你快速找到适合自己的耳机&am…...

OpenFace 2.2.0:如何构建超越传统界限的面部行为分析系统?

OpenFace 2.2.0&#xff1a;如何构建超越传统界限的面部行为分析系统&#xff1f; 【免费下载链接】OpenFace OpenFace – a state-of-the art tool intended for facial landmark detection, head pose estimation, facial action unit recognition, and eye-gaze estimation.…...

别再只调PID了!从一场起重机大赛看机器人设计的系统思维:结构、电源与控制的平衡艺术

从起重机大赛看机器人设计的系统思维&#xff1a;结构、电源与控制的平衡艺术 在机器人设计领域&#xff0c;我们常常陷入对单一技术点的过度关注——比如如何优化PID参数、选择哪种传感器、使用什么控制算法。然而&#xff0c;真正决定一个机器人系统成败的&#xff0c;往往是…...

终极音频解锁指南:qmcdump让QQ音乐文件自由播放

终极音频解锁指南&#xff1a;qmcdump让QQ音乐文件自由播放 【免费下载链接】qmcdump 一个简单的QQ音乐解码&#xff08;qmcflac/qmc0/qmc3 转 flac/mp3&#xff09;&#xff0c;仅为个人学习参考用。 项目地址: https://gitcode.com/gh_mirrors/qm/qmcdump 你是否在QQ音…...

FontCenter:三分钟解决AutoCAD字体缺失的终极方案

FontCenter&#xff1a;三分钟解决AutoCAD字体缺失的终极方案 【免费下载链接】FontCenter AutoCAD自动管理字体插件 项目地址: https://gitcode.com/gh_mirrors/fo/FontCenter 你是否曾经在打开AutoCAD图纸时&#xff0c;看到文字显示为问号或乱码&#xff1f;是否因为…...

EPLAN进阶实战:基于STEP模型的智能箱柜定义与高效拼柜流程详解

1. STEP模型导入与基础检查 第一次接触STEP格式的箱柜模型时&#xff0c;我和很多电气工程师一样犯过不少低级错误。记得有次项目赶进度&#xff0c;直接导入模型就开始操作&#xff0c;结果后面发现根本没法用&#xff0c;白白浪费了两天时间。现在我把这些经验教训总结成一套…...

别再手动挂载了!CentOS 7.6服务器数据盘一键挂载与开机自启保姆级教程(含fdisk分区详解)

CentOS 7.6数据盘智能挂载全攻略&#xff1a;从分区到自启的零失误实践 刚接触Linux服务器运维时&#xff0c;最让人头疼的莫过于那块"看得见却用不了"的数据盘。每次重启后都要重新挂载的繁琐操作&#xff0c;不仅浪费时间&#xff0c;更可能因操作失误导致数据丢失…...