linux上的通用拍照程序

news2024/9/22 3:29:44

最近因为工作需要,在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)

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2020年06月 Scratch(三级)真题解析#中国电子学会#全国青少年软件编程等级考试

Scratch等级考试(1~4级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 执行以下脚本后舞台上的角色将 ? A:先克隆自身,克隆体出现后被删除。 B:先克隆自身,克隆体出现后删除本体。 C:克隆出自身后本体与克隆体同时被删除。 D:克隆出自身后本体与克…

Rola详解国外住宅IP代理选择的8个方法,稳定的海外IP哪个靠谱?

一、国外住宅IP代理是什么? 代理服务器充当您和互联网之间的网关。它是一个中间服务器,将最终用户与他们浏览的网站分开。如果您使用国外代理IP,互联网流量将通过国外代理服务器流向您请求的地址。然后,请求通过同一个代理服务器…

python opencv 放射变换和图像缩放-实现图像平移旋转缩放

python opencv 放射变换和图像缩放-实现图像平移旋转缩放 我们实现这次实验主要用到cv2.resize和cv2.warpAffine cv2.warpAffine主要是传入一个图像矩阵,一个M矩阵,输出一个dst结果矩阵,计算公式如下: cv2.resize则主要使用fx&…

python之TCP的网络应用程序开发

文章目录 版权声明python3编码转换socket类的使用创建Socket对象Socket对象常用方法和参数使用示例服务器端代码客户端代码 TCP客户端程序开发流程TCP服务端程序开发流程TCP网络应用程序注意点socket之send和recv原理剖析send原理剖析recv原理剖析send和recv原理剖析图 多任务版…

【管理运筹学】背诵手册(五)| 动态规划

五、动态规划 基本概念 阶段(Stage):将所给问题的过程,按时间或空间特征分解成若干相互联系的阶段,以便按次序去求解每阶段的解,常用字母 k k k 表示。 状态(State):…

iOS APP包分析工具 | 京东云技术团队

介绍 分享一款用于分析iOSipa包的脚本工具,使用此工具可以自动扫描发现可修复的包体积问题,同时可以生成包体积数据用于查看。这块工具我们团队内部已经使用很长一段时间,希望可以帮助到更多的开发同学更加效率的优化包体积问题。 工具下载…

HTB Codify WriteUp

Codify 2023年11月7日 20:59:48user nmap ➜ Codify nmap -A 10.10.11.239 Starting Nmap 7.80 ( https://nmap.org ) at 2023-11-07 21:00 CST Nmap scan report for bogon (10.10.11.239) Host is up (0.14s latency). Not shown: 997 closed ports PORT STATE SERVI…

An issue was found when checking AAR metadata

一、报错信息 An issue was found when checking AAR metadata:1. Dependency androidx.activity:activity:1.8.0 requires libraries and applications that depend on it to compile against version 34 or later of the Android APIs.:app is currently compiled against …

spark的算子

spark的算子 1.spark的单Value算子 Spark中的单Value算子是指对一个RDD中的每个元素进行操作,并返回一个新的RDD。下面详细介绍一些常用的单Value算子及其功能: map:逐条映射,将RDD中的每个元素通过指定的函数转换成另一个值&am…

009 OpenCV 二值化 threshold

一、环境 本文使用环境为: Windows10Python 3.9.17opencv-python 4.8.0.74 二、二值化算法 2.1、概述 在机器视觉应用中,OpenCV的二值化函数threshold具有不可忽视的作用。主要的功能是将一幅灰度图进行二值化处理,以此大幅降低图像的数…

app小程序定制的重点|软件定制开发|网站搭建

app小程序定制的重点|软件定制开发|网站搭建 App小程序定制开发是近年来快速发展的一项技术服务,随着移动互联网的普及和用户需求的不断升级,越来越多的企业和个人开始关注和需求定制化的小程序开发。那么,对于app小程序定制开发来说&#xf…

第十五届蓝桥杯(Web 应用开发)模拟赛 1 期-大学组(详细分析解答)

目录 1.动态的Tab栏 1.1 题目要求 1.2 题目分析 1.3 源代码 2.地球环游 2.1 题目要求 2.2 题目分析 2.3 源代码 3.迷惑的this 3.1 题目要求 3.2 题目分析 3.3 源代码 4.魔法失灵了 4.1 题目要求 4.2 题目分析 4.3 源代码 5.燃烧你的卡路里 5.1 题目要求 5.2…

U-boot(四):start_armboot

本文主要探讨210的uboot启动的第二阶段,主要函数为start_armboot。 uboot 一阶段初始化SoC内部部件(看门狗、时钟等),初始化DDR,重定位 二阶段初始化其余硬件(iNand、网卡芯片)以及命令、环境变量等 启动打印硬件信息,进入bootdelay,读秒完后执行bootc…

(2023码蹄杯)省赛(初赛)第二场真题(原题)(题解+AC代码)

题目1&#xff1a;MC0214捡麦子 码题集OJ-捡麦子 (matiji.net) 思路: 1.第n米在前n-1米的基础上多加一个n个麦子&#xff0c;那么直接从1开始枚举&#xff0c;累加答案即可 AC_Code:C #include<bits/stdc.h> using namespace std;int main( ) {int n; cin>>n;…

ES6 — ES14 新特性

一、ES6 新特性&#xff08;2015&#xff09; 1. let和const 在ES6中&#xff0c;新增了let和const关键字&#xff0c;其中 let 主要用来声明变量&#xff0c;而 const 通常用来声明常量。let、const相对于var关键字有以下特点&#xff1a; 特性varletconst变量提升✔️全局…

NX二次开发UF_CAM_update_list_object_customization 函数介绍

文章作者&#xff1a;里海 来源网站&#xff1a;https://blog.csdn.net/WangPaiFeiXingYuan UF_CAM_update_list_object_customization Defined in: uf_cam.h int UF_CAM_update_list_object_customization(tag_t * object_tags ) overview 概述 This function provids the…

Elasticsearch:ES|QL 函数及操作符

如果你对 ES|QL 还不是很熟悉的话&#xff0c;请阅读之前的文章 “Elasticsearch&#xff1a;ES|QL 查询语言简介​​​​​​​”。ES|QL 提供了一整套用于处理数据的函数和运算符。 功能分为以下几类&#xff1a; 目录 ES|QL 聚合函数 AVG COUNT COUNT_DISTINCT 计数为近…

web:[WUSTCTF2020]朴实无华

题目 点开页面显示如下 页面显示了一行报错&#xff1a;Cannot modify header information - headers already sent by (output started at /var/www/html/index.php:3) in /var/www/html/index.php on line 4 意思为不能修改报头信息-报头已经发送(输出开始于/var/www/html/i…

Ubuntu20.04 install pnpm

npm install -g pnpm referrence link: Installation | pnpmPrerequisiteshttps://pnpm.io/installation

嵌入式系统在工业自动化中的应用

嵌入式系统在工业自动化中的应用非常广泛&#xff0c;它们通过集成控制和实时响应能力&#xff0c;实现了生产线的自动化、智能化和高效化。以下将详细介绍嵌入式系统在工业自动化中的几个重要应用领域&#xff0c;并提供一些示例代码。 1. PLC&#xff08;可编程逻辑控制器&a…