基于PyQt5实现的PDF小工具_pyqt5写pdf合成工具-程序员宅基地

技术标签: python  笔记  PyQt5  pyqt  拆分合并  mupdf  添加目录  


基于PyQt5实现的PDF工具箱

前言

PDF 是一种便携,易查找的电子书,在学习工作中我们可能需要翻阅大量的参考书籍,实体书往往贵且笨重,如果需要经常翻阅,那么可以购置纸质书。但是大多数时候我们可能只是需要查阅书中的某一部分,如此电子书便具有不可比拟的优势,互联网上有者大量的电子书籍,且多数以pdf格式流传,其中一部分为文字版,一部分为扫描版。这些文件一般为自制的盗版书籍,往往缺少目录,或者扫描不清晰。以致索引困难,观感极差。

市面上也有很多pdf编辑器,如Adobe Acrobat PDF,PDF Element,福昕PDF编辑器等,但他们大多价格昂贵,且没有批量添加目录的功能,虽有OCR增强的功能,但是比较耗时,且在年迈的PC上容易卡死。此工具没有使用OCR,仅对扫描页面的图片逐一增强来改善PDF清晰度。

1. 使用模块

  1. pyqt5pymupdfpython3.7qt designerQCandyUi(可选)
  2. 指定下载源。可后加-i https://pypi.tuna.tsinghua.edu.cn/simple,如
pip3 install pymupdf -i https://pypi.tuna.tsinghua.edu.cn/simple
pip3 install pyqt5,pyqt5-tools
  1. 在终端输入 designer 可启动 QT Designer
  2. 注意:如果在电脑上已经安装过Ananconda,则pyqt版本过高可能会导致Spyder无法打开,此时请使用如下命令,更换版本,(不要频繁的使用conda update)
pip3 uninstall pyqt5
pip3 install pyqt5==5.12.0

版本号如果有误,终端会列出可安装的版本,选择其一输入即可

2. 编辑环境搭建

  1. IDE:PyCharm EduSpyderVS Code + Python 插件

  2. 上述工具择其一即可,其中PyCharm 配置后可一体化开发 配置教程-博客园配置教程-知乎

  3. 我没有配置,逐一进行

  4. 用途 工具/方法
    绘制用户界面 (UI) QT Designer
    UI转Python代码 pyuic5
    编写Python 代码 PyCharm / VS Code
    执行代码 python3.7 (PyCharm / VS Code)
    打包为可执行文件 (可选) pyinstaller

3.工具箱功能概述

​ 1.主界面 (使用QCandyUi美化后的截图)

imageMain.png

​ 2.添加目录 (详见生成的 demo.txt)

imageAdd.png

​ 3.文本增强

imageEnhance.png

​ 4.拆分文档

imageSplit.png

​ 5.合并文档

imageMerge.png

4.步骤解析

  1. 使用QT Designer 绘制界面(如上图) ,选择 QDialog without Button,拖拽控件,绘制后保存为.ui文件,我分别保存为PDFTools.ui add_UI.ui enhance_UI.ui merge_UI.ui split_UI.ui,绘制界面比较简单,但是控件命名应当添加适当的前缀或后缀加以区分。

  2. 使用pyuic5命令将ui文件转换为python代码,切换到项目文件夹,输入

    pyuic5 PDFTools.ui -o PDFTools.py
    

    依次对界面代码进行转换,生成后的py文件无需手动更改,当再次生成时会完全覆盖。

  3. 编写调用窗口和信号处理代码。pyqt延续了qt的设计思想,只要处理好信号与槽(可理解为触发事件与处理方法关联),那么编写项目也会得心应手。在编写过程中可查看qt类手册,pyqt中的方法大多与QT C++同名,但是少了丑陋的指针 ->,使代码不那么扎眼。具体见代码解析

  4. 推荐使用ipython对方法/类 进行测试

  5. 启动

    > python main.py
    
  6. 如果需要可进行打包

    pyinstaller --onefile --windowed --icon=PDF.ico main.py
    

5.项目组成

  1. 代码组成

    --------窗口信号处理----------
    main.py
    callAdd.py
    callEnhance.py
    callSplit.py
    callMerge.py
    --------窗口界面布局----------
    PDFTools.py
    add_UI.py
    enhance_UI.py
    split_UI.py
    merge_UI.py
    --------PDF处理函数----------
    addFunctins.py
    enhanceFunctions.py
    splitFunctions.py
    
  2. 用到的QT控件信号, 通过 connect 可关联方法

    控件 信号
    pushButton clicked
    spinBox valueChanged /editintFinished
    Slider sliderMoved / valueChanged
    radioButton toggled
    lineEdit textChanged
    checkBox stateChanged
    comboBox currentIndexChanged
  3. 用到的QT控件方法 ,大多数控件都有setText() 和text() 方法,不一一列举

    控件 方法
    textEdit setText() /text()
    lineEdit setText() / text()
    checkBox isChecked()
    tableWidget setItem()
    spinBox setReadOnly()
    lineEdit setReadOnly
  4. pymupdf中的方法

    名称 描述
    open 打开文件(pdf,图片)
    save 保存
    setToC 设置目录
    getPixmap 获取本页的图片
    insertPDF 插入PDF
    close 关闭
    pageCount 获取页码(属性)
  5. osPIL 中的方法

    方法 描述
    open 打开txt
    write 写入txt
    close 关闭文本
    strip 删除指定符号
    split 根据指定符号分割字符串
    len 计算长度,数量
    range 连续的数
    replace 替换指定字符串
    os.getcwd() 获取当前文件路径
    os.path.exists() 是否存在文件夹
    os.makedirs() 创建文件夹
    os.remove() 删除文件
    Image.open 打开图片
    ImageEnhance.Contrast 增强方法

6.主要代码展示 ,完整项目详见PDFTools

main.py展示了 创建类,关联信号与槽,创建窗口的一般途径

import ctypes
import sys

from PyQt5.QtWidgets import QApplication, QDialog

from callAdd import AddForm
from callEnhance import EnhanceForm
from callMerge import MergeForm
from callSplit import SplitForm
from PDFTools import Ui_Dialog  # or import *

class MyForm(QDialog):
    def __init__(self):
        super().__init__()
        self.ui=Ui_Dialog()
        self.ui.setupUi(self)
        self.ui.pushButton_add.clicked.connect(self.add_Window)
        self.ui.pushButton_enhance.clicked.connect(self.enhance_Window)
        self.ui.pushButton_split.clicked.connect(self.split_Window)
        self.ui.pushButton_merge.clicked.connect(self.merge_Window)
        self.show()
    
    def add_Window(self):
        self.w1=AddForm()
        self.w1.show()
        #self.hide()
        
    def enhance_Window(self):
        self.w2=EnhanceForm()
        self.w2.show()
       
    def split_Window(self):
        self.w3=SplitForm()
        self.w3.show()
        
    def merge_Window(self):
        self.w4=MergeForm()
        self.w4.show()
      
        
if __name__=="__main__":
    app=QApplication(sys.argv)

    #set taskbar icon
    myappid = 'mycompany.myproduct.subproduct.version' # arbitrary string
    ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)

    w=MyForm()
    w.show()
    sys.exit(app.exec_())

主要功能代码部分

callAdd.py 工程中的四个callxxx.py均独立启动使用

# 添加目录事件主要逻辑
def start(self):
    if self.ui.lineEdit_PDF.text() != "" and self.ui.lineEdit_TXT.text() != "":
        self.ui.textEdit_progress.setText("可以")
        self.pdf=add2pdf(self.doc, self.txt, self.offsetNum)
        if self.pdf != None:
            newname=self.ui.lineEdit_PDF.text().replace(".pdf","-new.pdf")
            self.pdf.save(newname)
            self.ui.textEdit_progress.setText("目录添加成功")
            else:
                self.ui.textEdit_progress.setText("缺少文件")

addFunctions.py

def add2pdf(pdffile, txtfile, offset):
    """
    添加目录到PDF,其中文件应为打开状态
    :param: pdffile , textfile , offset
    :return:pdffile
    """
    lines = txtfile.readlines()
    toc = []
    for line in lines:
        if line[0] == '#' or len(line.split()) == 0:
            continue
        level = get_level(line)
        title = get_title(line)
        page = get_page(line)
        toc.append([level, title, page])
    pdffile.setToC(toc)
    return pdffile

callEnhance.py

#增强pdf 事件逻辑代码
def w2_start(self):
        oldFileName = self.ui.lineEdit_w2_openPDF.text()
        newFileName = oldFileName.replace(".pdf", "[enhanced].pdf")

        if oldFileName != '':
            doc = fitz.open(oldFileName)
            enhanceDoc = fitz.open()
            num = doc.pageCount
            baseBar = 1.0 / num * 100

            for i in range(num):
                imgpdf = getEnhancedPdf(doc[i], i, self.saveFlag,
                [self.colorValue, self.contrastValue, self.sharpnessValue,
                                         self.brightnessValue], 2)

                enhanceDoc.insertPDF(imgpdf)
                self.ui.label_w2_tips.setText("正在处理第 %d 页" % i)
                self.ui.progressBar_w2.setValue(i * baseBar)

            enhanceDoc.save(newFileName)
            doc.close()
            enhanceDoc.close()

            self.ui.progressBar_w2.setValue(100)
            self.ui.label_w2_tips.setText("处理完毕")

enhanceFunctions.py

"""
===============================================================================
    ImageEnhance.Color(image)      色彩平衡。 0-黑白,1-原图(可为小数,可 > 1)
    ImageEnhance.Contrast(image)   对比度。   0-灰色图像,1-原图
    ImageEnhance.Brightness(image) 亮度。     0.0-黑色图像,1-原图
    ImageEnhacne.Sharpness(image)  锐化。0.0是模糊图像,1.0是原始图像,2.0是锐化图像
===============================================================================
    上述类的增强方法都为 enhance(factor), 显示用show(),见下例子
===============================================================================
    from PIL import Image,ImageEnhance

    img=Image.open("name.png")
    # 下面的 Contrast 可换为 Color, Brightness, Sharpness 之一, 其他不变
    enhanceImg=ImageEnhance.Contrast(img)
    newImage=enhanceImg.enhance(2)
    newImage.show()
    newImage.save("new.png")
===============================================================================
"""

import fitz
import os
from PIL import Image, ImageEnhance

# 设置缩放及旋转角度
def setZoom(zoom_xy):  # 设置为 2
    rotate = int(0)  # 设置图片的旋转角度
    zoom_x = zoom_xy  # 设置图片相对于PDF文件在X轴上的缩放比例
    zoom_y = zoom_xy  # 设置图片相对于PDF文件在Y轴上的缩放比例
    trans = fitz.Matrix(zoom_x, zoom_y).preRotate(rotate)
    return trans


def getEnhancedPdf(docPage, index, saveFlag=False, factor=[1.0, 1.0, 1.0, 1.0], zoom_xy=2):
    """
    获取图像,并增加对比度
    :param : docPage: doc[i]
    :param :factor : color, contrast, sharpness, brightness
    :param :zoom_xy : enlargement factor
    :return : a page of enhanced pdf 
    """
    pix = docPage.getPixmap(matrix=setZoom(zoom_xy), alpha=False)
    img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)

    enhanceTemp1 = ImageEnhance.Color(img)
    pilImg1 = enhanceTemp1.enhance(factor[0])  # PIL img

    enhanceTemp2 = ImageEnhance.Contrast(pilImg1)
    pilImg2 = enhanceTemp2.enhance(factor[1])  # PIL img

    enhanceTemp3 = ImageEnhance.Sharpness(pilImg2)
    pilImg3 = enhanceTemp3.enhance(factor[2])  # PIL img

    enhanceTemp4 = ImageEnhance.Brightness(pilImg3)
    pilImg4 = enhanceTemp4.enhance(factor[3])  # PIL img

    # pilImg.show()
    folder = os.getcwd() + '/split'  # 创建文件夹
    if not os.path.exists(folder):
        os.makedirs(folder)
    imgName = folder + "/splitImage(" + "%04d" % index + ").png"
    pilImg4.save(imgName)
    fitImg = fitz.open(imgName)
    if saveFlag == False:
        os.remove(imgName)
    pdfBytes = fitImg.convertToPDF()
    imgpdf = fitz.open("pdf", pdfBytes)
    return imgpdf

callSplit.py

def w3_startSplit(self):
    if self.splitMode==0 or self.pathStr=='':
    self.tipStr="未添加pdf文件 或 未设置拆分模式 ! "
    elif self.splitMode==1:
    self.tipStr=split_pdf_same_page(self.pathStr,self.pageSame)
    elif self.splitMode==2:
    self.tipStr=split_pdf_custom_page(self.pathStr,self.pageStr)
    else:
    pass
    self.ui.label_w3_tip.setText(self.tipStr)

splitFunctions.py

# -*- coding: utf-8 -*-
"""
Created on Fri Jul 19 09:18:18 2020
@author: cherish
"""

import fitz

def split_pdf_same_page(pathStr, page):
    """
    将pdf 拆分为 多个pdf文件,每page页为一部分
    :param: pathStr :pdf路径的字符串, page : int 分割区间
    :return: true
    """
    doc = fitz.open(pathStr)
    # doc1 = fitz.open() # 空 pdf 文件
    docPage = doc.pageCount
    if page > docPage or page ==0 :
        return "错误 : 页码超出范围,或 分割区间为0 !"

    num = docPage//page  # 可以拆成 page 页的部分
    for i in range(num):  # 0 到 num-1
        doc1 = fitz.open()  # 空 pdf 文件
        doc1.insertPDF(doc, from_page=page*i, to_page=page*(i+1)-1, start_at=-1)
        partName = '[part'+str(i)+'].pdf'
        newName = pathStr.replace(".pdf", partName)
        doc1.save(newName)
        doc1.close()

    surplus = docPage % page  # 最后少于 page 的部分
    if surplus != 0:
        doc1 = fitz.open()  # 空 pdf 文件
        doc1.insertPDF(doc, from_page=page*num, to_page=docPage-1, start_at=-1)
        partName = '[part'+str(num)+'].pdf'
        newName = pathStr.replace(".pdf", partName)
        doc1.save(newName)
        doc1.close()
    return "拆分成功"


def split_pdf_custom_page(pathStr, pageStr):
    """
    将pdf拆分为自定义页码的多个pdf文件
    :param: str ,pdf路径的字符串, pageStr: 自定义页码的字符串
    :return: Error page string / True
    """
    doc = fitz.open(pathStr)
    docPage = doc.pageCount
    pageStr = pageStr.replace(',', ',')  # 如果是中文逗号
    pageList = pageStr.split(",")

    for page in pageList:
        pse = page.split('-')  # page start and end
        ps = int(pse[0])
        pe = int(pse[0]) if len(pse) == 1 else int(pse[1])
        if ps > pe or pe > docPage:
            return "请检查>  "+ page + " 是否有误 !"  # error: page part

        doc1 = fitz.open()  # 创建 空pdf文件
        doc1.insertPDF(doc, from_page=ps-1, to_page=pe-1, start_at=-1)
        partName = '[page'+str(ps)+'-' + str(pe)+'].pdf'
        newName = pathStr.replace(".pdf", partName)
        doc1.save(newName)
        doc1.close()
    return "拆分成功"

merge_UI.py 设置表格列宽,使用pyuic后会覆盖

self.tableWidget_w4_fileList.setColumnWidth(0, 450)
self.tableWidget_w4_fileList.setColumnWidth(1, 70)

callMerge.py

def w4_start(self):
    if self.rowCount != 0:
    doc = fitz.open(self.ui.tableWidget_w4_fileList.item(0, 0).text())
    basebar = int(1.0 / self.rowCount * 100)

    for i in range(1, self.rowCount):
    doc1 = fitz.open(self.ui.tableWidget_w4_fileList.item(i, 0).text())
    doc.insertPDF(doc1)
    self.ui.progressBar_w4_bar.setValue(i * basebar)
    self.ui.progressBar_w4_bar.setValue(100)
    doc.save("merge.pdf")
    doc.close()

demo.txt 该文件是存放目录的文件,要求和格式见下,在打开添加目录窗口的同时会自动生成该文件

# =========================================================================
# 
# 建议在如下网站搜集目录:
#           1.京东图书 https://book.jd.com/
#           2.豆瓣读书 https://book.douban.com/
#           3.当当图书 http://book.dangdang.com/
#           4.文泉书局 https://wqbook.wqxuetang.com/
# 
# =========================================================================
# 标准格式1 如下:(空行不影响)
# 特征 :两部分构成: 标题 + 空格 + 页码
# 标题中含 '第' 和 '章' 的会识别为一级标题,其他为二级标题
# =========================================================================

第1章概述     1
什么是OpenCV        1
OpenCV怎么用        2
什么是计算机视觉     3
OpenCV的起源        6
OpenCV的结构    7
使用IPP来加速OpenCV     8
谁拥有OpenCV    9
下载和安装OpenCV    9

# ==========================================================================
# 标准格式2 如下 
# 特征 :章序/节序 + 空格 + 标题 + 空格 + 页码 (空格用于区分各元素)
# 无 章节序 的 默认识别为 二级标题 ,若想设置为一级标题,请在前加 '@ ',
# 一般 需要区分的是 前言 目录 附录 参考文献 这些 
# 节序中有一个点 表二级标题(如 6.1 ),两个点 表三级标题(如 1.2.3),以此类推
# ==========================================================================

第6章 支持向量机 121
6.1 间隔与支持向量 121
6.2 对偶问题 123
6.3 核函数 126
6.4 软间隔与正则化 129
6.5 支持向量回归 133
6.6 核方法 137
6.7 阅读材料 139
习题 141
休息一会儿 145
@ 参考文献 520

# ==========================================================================

7.ToDo List

- [ ] 自定义样式表
- [ ] 自动爬取目录
- [ ] img2pdf

8.结语

第一次使用pyqt5写一个完整的项目,用时大概六天。前期绘制界面一天。后期逐一完善各个功能四天,写文档,改bug一天。最大的感受就是Python语法友好,轮子很全。本次项目也是熟悉pyqt的过程。写完本应用基本掌握了常用的控件,信号,槽。整体而言,使用pyqt5编写一些小工具还是很方便的。至于执行效率,一般的小项目基本体现不出来。

PyQt5优点:相较于QT Creator,python代码比较优雅;相较于 C# Winform/WPF ,python拥有较多的库。可用样式表setStyleSheet美化控件

缺点:QT Designer 可设置参数偏少,需要使用代码设置,控件不够美观,打包文件偏大。

9.参考

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_34360180/article/details/107482554

智能推荐

5个超厉害的资源搜索网站,每一款都可以让你的资源满满!_最全资源搜索引擎-程序员宅基地

文章浏览阅读1.6w次,点赞8次,收藏41次。生活中我们无时不刻不都要在网站搜索资源,但就是缺少一个趁手的资源搜索网站,如果有一个比较好的资源搜索网站可以帮助我们节省一大半时间!今天小编在这里为大家分享5款超厉害的资源搜索网站,每一款都可以让你的资源丰富精彩!网盘传奇一款最有效的网盘资源搜索网站你还在为找网站里面的资源而烦恼找不到什么合适的工具而烦恼吗?这款网站传奇网站汇聚了4853w个资源,并且它每一天都会持续更新资源;..._最全资源搜索引擎

Book类的设计(Java)_6-1 book类的设计java-程序员宅基地

文章浏览阅读4.5k次,点赞5次,收藏18次。阅读测试程序,设计一个Book类。函数接口定义:class Book{}该类有 四个私有属性 分别是 书籍名称、 价格、 作者、 出版年份,以及相应的set 与get方法;该类有一个含有四个参数的构造方法,这四个参数依次是 书籍名称、 价格、 作者、 出版年份 。裁判测试程序样例:import java.util.*;public class Main { public static void main(String[] args) { List <Book>_6-1 book类的设计java

基于微信小程序的校园导航小程序设计与实现_校园导航微信小程序系统的设计与实现-程序员宅基地

文章浏览阅读613次,点赞28次,收藏27次。相比于以前的传统手工管理方式,智能化的管理方式可以大幅降低学校的运营人员成本,实现了校园导航的标准化、制度化、程序化的管理,有效地防止了校园导航的随意管理,提高了信息的处理速度和精确度,能够及时、准确地查询和修正建筑速看等信息。课题主要采用微信小程序、SpringBoot架构技术,前端以小程序页面呈现给学生,结合后台java语言使页面更加完善,后台使用MySQL数据库进行数据存储。微信小程序主要包括学生信息、校园简介、建筑速看、系统信息等功能,从而实现智能化的管理方式,提高工作效率。

有状态和无状态登录

传统上用户登陆状态会以 Session 的形式保存在服务器上,而 Session ID 则保存在前端的 Cookie 中;而使用 JWT 以后,用户的认证信息将会以 Token 的形式保存在前端,服务器不需要保存任何的用户状态,这也就是为什么 JWT 被称为无状态登陆的原因,无状态登陆最大的优势就是完美支持分布式部署,可以使用一个 Token 发送给不同的服务器,而所有的服务器都会返回同样的结果。有状态和无状态最大的区别就是服务端会不会保存客户端的信息。

九大角度全方位对比Android、iOS开发_ios 开发角度-程序员宅基地

文章浏览阅读784次。发表于10小时前| 2674次阅读| 来源TechCrunch| 19 条评论| 作者Jon EvansiOSAndroid应用开发产品编程语言JavaObjective-C摘要:即便Android市场份额已经超过80%,对于开发者来说,使用哪一个平台做开发仍然很难选择。本文从开发环境、配置、UX设计、语言、API、网络、分享、碎片化、发布等九个方面把Android和iOS_ios 开发角度

搜索引擎的发展历史

搜索引擎的发展历史可以追溯到20世纪90年代初,随着互联网的快速发展和信息量的急剧增加,人们开始感受到了获取和管理信息的挑战。这些阶段展示了搜索引擎在技术和商业模式上的不断演进,以满足用户对信息获取的不断增长的需求。

随便推点

控制对象的特性_控制对象特性-程序员宅基地

文章浏览阅读990次。对象特性是指控制对象的输出参数和输入参数之间的相互作用规律。放大系数K描述控制对象特性的静态特性参数。它的意义是:输出量的变化量和输入量的变化量之比。时间常数T当输入量发生变化后,所引起输出量变化的快慢。(动态参数) ..._控制对象特性

FRP搭建内网穿透(亲测有效)_locyanfrp-程序员宅基地

文章浏览阅读5.7w次,点赞50次,收藏276次。FRP搭建内网穿透1.概述:frp可以通过有公网IP的的服务器将内网的主机暴露给互联网,从而实现通过外网能直接访问到内网主机;frp有服务端和客户端,服务端需要装在有公网ip的服务器上,客户端装在内网主机上。2.简单的图解:3.准备工作:1.一个域名(www.test.xyz)2.一台有公网IP的服务器(阿里云、腾讯云等都行)3.一台内网主机4.下载frp,选择适合的版本下载解压如下:我这里服务器端和客户端都放在了/usr/local/frp/目录下4.执行命令# 服务器端给执_locyanfrp

UVA 12534 - Binary Matrix 2 (网络流‘最小费用最大流’ZKW)_uva12534-程序员宅基地

文章浏览阅读687次。题目:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=93745#problem/A题意:给出r*c的01矩阵,可以翻转格子使得0表成1,1变成0,求出最小的步数使得每一行中1的个数相等,每一列中1的个数相等。思路:网络流。容量可以保证每一行和每一列的1的个数相等,费用可以算出最小步数。行向列建边,如果该格子是_uva12534

免费SSL证书_csdn alphassl免费申请-程序员宅基地

文章浏览阅读504次。1、Let's Encrypt 90天,支持泛域名2、Buypass:https://www.buypass.com/ssl/resources/go-ssl-technical-specification6个月,单域名3、AlwaysOnSLL:https://alwaysonssl.com/ 1年,单域名 可参考蜗牛(wn789)4、TrustAsia5、Alpha..._csdn alphassl免费申请

测试算法的性能(以选择排序为例)_算法性能测试-程序员宅基地

文章浏览阅读1.6k次。测试算法的性能 很多时候我们需要对算法的性能进行测试,最简单的方式是看算法在特定的数据集上的执行时间,简单的测试算法性能的函数实现见testSort()。【思想】:用clock_t计算某排序算法所需的时间,(endTime - startTime)/ CLOCKS_PER_SEC来表示执行了多少秒。【关于宏CLOCKS_PER_SEC】:以下摘自百度百科,“CLOCKS_PE_算法性能测试

Lane Detection_lanedetectionlite-程序员宅基地

文章浏览阅读1.2k次。fromhttps://towardsdatascience.com/finding-lane-lines-simple-pipeline-for-lane-detection-d02b62e7572bIdentifying lanes of the road is very common task that human driver performs. This is important ..._lanedetectionlite

推荐文章

热门文章

相关标签