【进阶OpenCV】 (8)--摄像头操作--->识别文档内容

文章目录

  • 摄像头操作
    • 1. 打开摄像头
    • 2. 识别画面预处理
    • 3. 轮廓检测
    • 4. 轮廓近似
    • 5. 透视变换
      • 5.1 定义order_point(pts)方法:
      • 5.2 定义four_point_transform(image,pts)方法:
      • 5.3 代码应用
    • 6. 关闭图像窗口
    • 7. 完整代码展示
  • 总结

摄像头操作

本篇我们来介绍,如何打开摄像头来识别文档。

思路

  1. 打开摄像头。
  2. 描绘出摄像头识别画面中的所有轮廓
  3. 那么,轮廓有了,如何找到独属于文档的轮廓呢?我们知道,一般的文档都是长方形的,利用这一点,我们可以通过轮廓近似的方法,看看哪些轮廓是可以通过四点定位的,从而取出文档轮廓。
  4. 识别之后,通过透视变换方法,将文档规整的单独展示出来。

1. 打开摄像头

通过**cv2.VideoCapture()**方法,当括号内为0时,打开电脑摄像头;为1时,打开外接摄像头。

cap = cv2.VideoCapture(0) # 打开摄像头
if not cap.isOpened(): # 打开失败‘
    print("Cannot open camera")
    exit()

2. 识别画面预处理

使用**cap.read()**方法读取摄像头画面,将画面转化为灰度图,进行高斯滤波去除噪声:

def cv_show(name,img):
    cv2.imshow(name,img)
    cv2.waitKey(60)
    
while True:
    flag = 0 # 用于标识,当前是否检测到文件
    ret,image = cap.read() # 如果正确读取,ret为True
    orig = image.copy()
    if not ret:
        print("不能读取摄像头")
        break
    cv_show("image",image)

    gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY) # 将图像转化为灰度图
    # 预处理
    gray = cv2.GaussianBlur(gray,(5,5),0) # 高斯滤波
    edged = cv2.Canny(gray,75,200)
    cv_show('1',edged)

注意!!!:以下每点操作都在主循环while True中。

3. 轮廓检测

通过cv2.findContours()方法查询轮廓,并将其在原图上描绘出来:

# 轮廓检测
cnts = cv2.findContours(edged.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[1]
cnts = sorted(cnts,key=cv2.contourArea,reverse=True)[:3]
image_contours = cv2.drawContours(image,cnts,-1,(0,255,0),2)
cv_show("image_contours",image_contours)

4. 轮廓近似

遍历每一个检测到的轮廓,使用**cv2.approxPolyDP()**方法对其进行轮廓近似,因为文档是长方形的,接着将近似轮廓只需要四个点就组成的轮廓取出,意味着成功识别到了文档:

# 遍历轮廓
for c in cnts:
    # 计算轮廓近似
    peri = cv2.arcLength(c,True) # 计算轮廓的周长

    approx = cv2.approxPolyDP(c,0.05 * peri,True) # 轮廓近似
    # C表示输入的点集
    # epsilon表示从原始轮廓到近似轮廓的最大距离,它是一个准确参数
    # True表示封闭的
    area = cv2.contourArea(approx)

    # 4个点的时候就拿出来
    if area > 20000 and len(approx) == 4:
        screenCnt = approx
        flag = 1
        print(peri,area)
        print('检测到文档')
        break

5. 透视变换

率先将透视变换的方法通过函数形式编写出来以便于调用(定位到四个角点,然后将其透视变换到一个矩阵上):

5.1 定义order_point(pts)方法:

用于将给定的四个点(通常是从图像中检测到的轮廓点或角点)按照特定的顺序排列:左上角(tl)、右上角(tr)、右下角(br)和左下角(bl)。

过程

  • 首先,计算每个点坐标的和 s,通过 np.argmin(s) 和 **np.argmax(s)**找到 y 值最小(最上)和最大(最下)的两个点,分别作为矩形的顶部和底部点。

  • 然后,计算每对相邻点之间 x 坐标的差 diff,通过 np.argmin(diff)np.argmax(diff) 找到 x 值变化最小(最左,即左侧边界上的点,假设点按顺时针或逆时针顺序给出)和最大(最右,即右侧边界上的点)的两个点,分别作为矩形的左侧和右侧点。

  • 输出:rect是一个形状为 (4, 2) 的 NumPy 数组,包含了按左上角、右上角、右下角、左下角顺序排列的四个点。

def order_point(pts):
    rect = np.zeros((4,2),dtype="float32")
    s = pts.sum(axis=1)
    rect[0] = pts[np.argmin(s)]
    rect[2] = pts[np.argmax(s)]
    diff = np.diff(pts,axis=1)
    rect[1] = pts[np.argmin(diff)]
    rect[3] = pts[np.argmax(diff)]
    return rect

5.2 定义four_point_transform(image,pts)方法:

这个函数使用 order_point函数得到的点来对输入图像进行透视变换,使得这四个点映射到一个矩形上。

  • 输入:image是要进行透视变换的输入图像,pts是图像中检测到的四个点的坐标。

  • 过程

  1. 首先,调用 order_point(pts) 来获取按特定顺序排列的四个点(tl, tr, br, bl)。

  2. 然后,计算这四个点形成的矩形的宽度和高度,以确保变换后的图像能够包含整个矩形区域。

  3. 接着,定义一个目标矩形 dst,其四个角点映射到变换后的图像的 (0,0)、(maxwidth-1,0)、(maxwidth-1,maxheight-1) 和 (0,maxheight-1) 位置。

  4. 使用 OpenCV 的 **cv2.getPerspectiveTransform(rect, dst)**函数计算透视变换矩阵 M

  5. 最后,使用 **cv2.warpPerspective(image, M, (maxwidth, maxheight))**对输入图像进行透视变换,得到变换后的图像。

  • 输出warped是经过透视变换后的图像。
def four_point_transform(image,pts):
    # 获取输入坐标点
    rect = order_point(pts)
    (tl,tr,br,bl) = rect
    # 计算输入的w和h值
    widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
    widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
    maxwidth = max(int(widthA),int(widthB))
    heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
    heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
    maxheight = max(int(heightA), int(heightB))
    # 变换后对应坐标位置
    dst = np.array([[0,0],[maxwidth - 1,0],
                    [maxwidth - 1,maxheight - 1],[0,maxheight - 1]],dtype="float32")

    M = cv2.getPerspectiveTransform(rect,dst)
    warped = cv2.warpPerspective(image,M,(maxwidth,maxheight))
    # 返回变化后结果
    return warped

5.3 代码应用

将透视变换后的图像进行二值化处理,是内容呈现更清晰:

if flag == 1:
    # 展示结果
    image_contours = cv2.drawContours(image,[screenCnt],0,(0,255,0),2)
    cv_show("image",image_contours)
    # 透视变换
    warped = four_point_transform(orig,screenCnt.reshape(4,2))
    cv_show("warped",warped)
    # 二值处理
    warped = cv2.cvtColor(warped,cv2.COLOR_RGB2GRAY)
    ref = cv2.threshold(warped,220,255,cv2.THRESH_BINARY)[1]
    # ref = cv2.threshold(warped, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
    cv_show("ref",ref)

6. 关闭图像窗口

对于视频画面的捕获,每一帧保存,需要通过**cap.release()**方法释放(写在主循环外哦!):

cap.release() # 释放捕获器-
cv2.destroyAllWindows() # 关闭图像窗口

7. 完整代码展示

import cv2
import numpy as np

def cv_show(name,img):
    cv2.imshow(name,img)
    cv2.waitKey(60)

def order_point(pts):
    rect = np.zeros((4,2),dtype="float32")
    s = pts.sum(axis=1)
    rect[0] = pts[np.argmin(s)]
    rect[2] = pts[np.argmax(s)]
    diff = np.diff(pts,axis=1)
    rect[1] = pts[np.argmin(diff)]
    rect[3] = pts[np.argmax(diff)]
    return rect
def four_point_transform(image,pts):
    # 获取输入坐标点
    rect = order_point(pts)
    (tl,tr,br,bl) = rect
    # 计算输入的w和h值
    widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
    widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
    maxwidth = max(int(widthA),int(widthB))
    heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
    heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
    maxheight = max(int(heightA), int(heightB))
    # 变换后对应坐标位置
    dst = np.array([[0,0],[maxwidth - 1,0],
                    [maxwidth - 1,maxheight - 1],[0,maxheight - 1]],dtype="float32")

    M = cv2.getPerspectiveTransform(rect,dst)
    warped = cv2.warpPerspective(image,M,(maxwidth,maxheight))
    # 返回变化后结果
    return warped

if __name__ == '__main__':
    cap = cv2.VideoCapture(0) # 打开摄像头
    if not cap.isOpened(): # 打开失败‘
        print("Cannot open camera")
        exit()

    while True:
        flag = 0 # 用于标识,当前是否检测到文件
        ret,image = cap.read() # 如果正确读取,ret为True
        orig = image.copy()
        if not ret:
            print("不能读取摄像头")
            break
        cv_show("image",image)

        gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY) # 将图像转化为灰度图
        # 预处理
        gray = cv2.GaussianBlur(gray,(5,5),0) # 高斯滤波
        edged = cv2.Canny(gray,75,200)
        cv_show('1',edged)

        # 轮廓检测
        cnts = cv2.findContours(edged.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[1]

        cnts = sorted(cnts,key=cv2.contourArea,reverse=True)[:3]
        image_contours = cv2.drawContours(image,cnts,-1,(0,255,0),2)
        cv_show("image_contours",image_contours)

        # 遍历轮廓
        for c in cnts:
            # 计算轮廓近似
            peri = cv2.arcLength(c,True) # 计算轮廓的周长

            approx = cv2.approxPolyDP(c,0.05 * peri,True) # 轮廓近似
            # C表示输入的点集
            # epsilon表示从原始轮廓到近似轮廓的最大距离,它是一个准确参数
            # True表示封闭的
            area = cv2.contourArea(approx)

            # 4个点的时候就拿出来
            if area > 20000 and len(approx) == 4:
                screenCnt = approx
                flag = 1
                print(peri,area)
                print('检测到文档')
                break
        if flag == 1:
            # 展示结果
            image_contours = cv2.drawContours(image,[screenCnt],0,(0,255,0),2)
            cv_show("image",image_contours)
            # 透视变换
            warped = four_point_transform(orig,screenCnt.reshape(4,2))
            cv_show("warped",warped)
            # 二值处理
            warped = cv2.cvtColor(warped,cv2.COLOR_RGB2GRAY)
            ref = cv2.threshold(warped,220,255,cv2.THRESH_BINARY)[1]
            # ref = cv2.threshold(warped, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
            cv_show("ref",ref)

cap.release() # 释放捕获器-
cv2.destroyAllWindows() # 关闭图像窗口

总结

本篇介绍了:

如何通过打开摄像头,识别文档。

打开摄像头读取每一帧 ----> 识别轮廓 ----> 通过轮廓近似找到文档 -----> 透视变换取出文档 ----->二值化文档,使文档更清晰。

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

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

相关文章

前端的全栈混合之路Meteor篇:分布式数据协议DDP深度剖析

本文属于进阶篇,并不是太适合新人阅读,但纯粹的学习还是可以的,因为后续会实现很多个ddp的版本用于web端、nodejs端、安卓端和ios端,提前预习和复习下。ddp协议是一个C/S架构的协议,但是客户端也同时可以是服务端。 什…

SSD |(二)SSD主控

文章目录 📚控制器架构🐇PCIe和NVMe控制器前端子系统🐇NAND闪存控制器后端子系统🐇内存子系统🐇安全子系统🐇CPU计算子系统 📚控制器架构 控制器作为一个片上系统,处理来自用户端的…

【二分算法】——8个题目让你找到二分算法的感觉势如破竹

文章目录 1.二分查找2.在排序数组中查找元素的第一个和最后一个位置3.x的平方根4.搜索插入位置5.山脉数组的峰顶索引6.寻找峰值7.寻找旋转排序数组中的最小值8.JZ53(2) 1.二分查找 https://leetcode.cn/problems/binary-search/ 思路: 标准的二分查找。给定一个有序数组和目…

【2024版本】Mac/Windows IDEA安装教程

IDEA 2024版本真的很强大,此外JDK发布了最新稳定版 JDK21 ,只有新版本支持JDK 21、JDK22。原来数据库插件不支持redis等一些NoSql的数据库的连接,如果要使用需要自己单独装收费的插件。直接打开idea就很吃内存了,再打开其他一大堆…

47 C 语言实战项目——家庭收支记账软件

目录 1 需求说明 1.1 菜单显示 1.2 登记收入 1.3 登记支出 1.4 显示收支明细 1.5 退出 2 流程分析 2.1 总流程图 2.2 登记收入流程图 2.3 登记支出流程图 2.4 收支明细流程图 2.5 退出流程图 3 代码实现 3.1 框架搭建 3.2 收支明细功能 3.3 登记收入功能 3.4 …

23年408数据结构

第一题: 解析: 第一点,我们要知道顺序存储的特点:优点就是随用随取,就是你想要查询第几个元素可以直接查询出来,时间复杂度就是O(1),缺点就是不适合删除和插入,因为每次删除和插入一…

【数据分享】2000—2023年我国省市县三级逐年植被覆盖度(FVC)数据(Shp/Excel格式)

之前我们分享过2000—2023年逐月植被覆盖度(FVC)栅格数据(可查看之前的文章获悉详情)和Excel和Shp格式的省市县三级逐月FVC数据(可查看之前的文章获悉详情),原始的逐月栅格数据来源于高吉喜学者…

青云AI算力创新:直击AI落地痛点 打造企业数智化基石

在当今这个数字化、智能化的时代,企业数字化转型、智能化升级应用实践在加速,AI算力已经成为企业数字化转型和智能化升级的重要基石,而AI算力在推动技术创新和业务增长中起到了关键作用。青云科技近日举办的AI算力发布会,标志着AI…

知识图谱入门——5:Neo4j Desktop安装和使用手册(小白向:Cypher 查询语言:逐步教程!Neo4j 优缺点分析)

Neo4j简介 Neo4j 是一个基于图结构的 NoSQL 数据库,专门用于存储、查询和管理图形数据。它的核心思想是使用节点、关系和属性来描述数据。图数据库非常适合那些需要处理复杂关系的数据集,如社交网络、推荐系统、知识图谱等领域。 与传统的关系型数据库…

如何快速给word文件加拼音?请跟着步骤完成吧

如何快速给word文件加拼音?在日常工作中,我们时常会遇到需要为Word文件中的文字添加拼音的情况,这尤其在教育、出版或国际交流等领域显得尤为重要。为文字配上拼音,不仅能帮助学习者准确发音,还能提升文档的可读性和普…

【JavaEE初阶】深入理解不同锁的意义,synchronized的加锁过程理解以及CAS的原子性实现(面试经典题);

前言 🌟🌟本期讲解关于锁的相关知识了解,这里涉及到高频面试题哦~~~ 🌈上期博客在这里:【JavaEE初阶】深入理解线程池的概念以及Java标准库提供的方法参数分析-CSDN博客 🌈感兴趣的小伙伴看一看小编主页&am…

【STM32单片机_(HAL库)】4-2-1【定时器TIM】定时器输出PWM实现呼吸灯实验

1.硬件 STM32单片机最小系统LED灯模块 2.软件 pwm驱动文件添加定时器HAL驱动层文件添加GPIO常用函数定时器输出PWM配置步骤main.c程序 #include "sys.h" #include "delay.h" #include "led.h" #include "pwm.h"int main(void) {HA…

【瑞萨RA8D1 CPK开发板】串口的使用和STDOUT输出重定向

串口 本次串口的使用关于时钟导致串口的波特率不对,坑了我很久的时间 使能时钟 串口发现一个问题就是,只能使用下边的时钟配置,修改时钟源和分频系数都会导致串口波特率不正常,这种问题出现在mdkrasc的使用场景之下&#xff1b…

adaptor lora基础

https://www.zhihu.com/question/508658141/answer/3340979311 adaptor和PEFT的区别:前者在模型子层后加一个小型的dense;后者直接稀疏化模型本身; Loading Pre-Trained Adapters — AdapterHub documentation CVPR 2024 | SD-DiT&#xff…

Python | Leetcode Python题解之第468题验证IP地址

题目: 题解: class Solution:def validIPAddress(self, queryIP: str) -> str:if queryIP.find(".") ! -1:# IPv4last -1for i in range(4):cur (len(queryIP) if i 3 else queryIP.find(".", last 1))if cur -1:return &q…

每日OJ题_牛客_小乐乐改数字_模拟_C++_Java

目录 牛客_小乐乐改数字_模拟 题目解析 C代码 Java代码 牛客_小乐乐改数字_模拟 小乐乐改数字_牛客题霸_牛客网 (nowcoder.com) 描述: 小乐乐喜欢数字,尤其喜欢0和1。他现在得到了一个数,想把每位的数变成0或1。如果某一位是奇数&#…

【网络安全】CVE-2024-46990: Directus环回IP过滤器绕过实现SSRF

未经许可,不得转载。 文章目录 背景漏洞详情受影响版本解决方案背景 Directus 是一款开源 CMS,提供强大的内容管理 API,使开发人员能够轻松创建自定义应用程序,凭借其灵活的数据模型和用户友好的界面备受欢迎。然而,Directus 存在一个漏洞,允许攻击者绕过默认的环回 IP …

大数据之——VWare、Ubuntu、CentOs、Hadoop安装配置

前言:这里很抱歉前几期考研专题以及PyTorch这些内容都没有更新,并不是没有在学了,而是事太鸡儿多了,前不久刚刚打完华为开发者比赛,然后有紧接着高数比赛、考研复习,因此这些后续文章都在草稿状态中&#x…

Allegro在PCB上开槽的三种方法操作指导

Allegro如何在PCB上开槽的三种方法操作指导 当PCB有特殊设计要求的时候,需要在PCB上开槽,Allegro支持在PCB上开槽操作,具体操作如下 以下图为例,需要在这个板框中间开槽 开方形槽 选择shape add rect命令 画在Board Geometry-o…

【技术详解】SpringMVC框架全面解析:从入门到精通(SpringMVC)

文章目录 【技术详解】SpringMVC框架全面解析:从入门到精通(SpringMVC)SpringMVC概述1. 三层架构与MVC架构区别1.1 三层架构1.2 MVC架构1.3前后端分离开发模式 2. SpringMVC环境搭建2.1 注解启动方式2.2 xml启动方式2.3 SpringMVC PostMan工具使用 3. SpringMVC 请求…