YOLOV4-车道线检测-车距离预测

1.前言

        最近在看华为的CANN框架,发现了一些很有意思的开源算法(本文所有的代码都出自华为开源git发布的代码),华为最近出了AI PRO开发板,想着现在开发板上用用(不想重新配置环境了,麻烦还累),看着代码有onnx的模型,就先用onnx实现,后续可能推出rknn的推理吧,谁知道呢。具体细节也不想写,无脑用就行。

2.代码准备

        代码是一个视频处理的程序,主要功能是使用YOLOv4模型进行目标检测,并结合车道检测,最后输出处理后的视频。

2.1 主要步骤

  1. 导入必要的库:导入了一系列常用的计算机视觉库,如OpenCV、numpy等,以及自定义的LaneFinder模块。
  2. 定义了一些常量和全局变量:包括类别标签、模型输入输出的尺寸、类别数量、锚点等。
  3. 定义了预处理函数preprocess:将输入的帧图像进行缩放和填充,使其符合模型的输入尺寸,并进行归一化处理。
  4. 定义了一些辅助函数:如计算两个框的重叠区域、计算IoU、应用非极大值抑制(NMS)等。
  5. 定义了模型输出解码函数decode_bbox:将模型输出的特征图转换为检测框的坐标和类别概率。
  6. 定义了后处理函数post_process:根据模型输出的结果进行NMS处理,并将检测结果转换为可读的格式。
  7. 定义了一些辅助函数:包括将标签转换为可读格式、处理帧图像等。
  8. 主函数main:读取视频帧,调用前述函数进行目标检测和车道检测,最后将结果写入输出视频文件中。

 2.2 JSON配置

        文件包含了相机校准矩阵、畸变系数、透视变换矩阵以及其他参数。

  1. cam_matrix(相机矩阵):相机内参矩阵,是用来描述相机的内部参数的一个3x3矩阵。其中包括了相机的焦距(fx、fy)、主点(cx、cy)等信息。在这个配置中,焦距分别为1156.94047、1152.13881,主点坐标为(665.948814, 388.784788)。

  2. dist_coeffs(畸变系数):相机的畸变系数,通常由径向畸变系数和切向畸变系数构成。这里包含了五个系数,分别是[-0.237638057, -0.0854041989, -0.000790999421, -0.000115882426, 0.105726054]。

  3. perspective_transform(透视变换矩阵):透视变换矩阵,用于将图像转换到鸟瞰图(俯视图)。该矩阵是一个3x3的矩阵,其中包含了变换的缩放、旋转和平移信息。

  4. pixels_per_meter(每米对应的像素数):这个参数表示在鸟瞰图中,每米对应的像素数。在水平方向上为46.56770571051312像素/m,在垂直方向上为33.06512376601635像素/m。

  5. WARPED_SIZE(鸟瞰图尺寸):进行透视变换后的图像尺寸,宽度为500像素,高度为600像素。

  6. ORIGINAL_SIZE(原始图像尺寸):原始图像的尺寸,宽度为1280像素,高度为720像素。

2.3 车道线检测

        LaneFinder.py是一个用于检测车道线的算法。以下是代码中各个函数的功能:

  1. get_center_shift(coeffs, img_size, pixels_per_meter): 计算车道线中心的偏移量。
  2. get_curvature(coeffs, img_size, pixels_per_meter): 计算车道线的曲率。
  3. LaneLineFinder: 一个类,用于检测车道线中的单条车道线。
  4. LaneFinder: 一个类,用于检测整个车道。它包括了左右两条车道线的检测。
  5. undistort(img): 对图像进行畸变校正。
  6. warp(img): 对图像进行透视变换,使车道线在图像中呈现平行。
  7. unwarp(img): 对透视变换后的图像进行逆变换,使车道线回到原始视角。
  8. equalize_lines(alpha): 对检测到的左右车道线进行均衡处理,使它们保持一定的间隔。
  9. find_lane(img, distorted=True, reset=False): 在图像中寻找车道线,包括畸变校正、透视变换、颜色过滤和车道线检测等步骤。
  10. draw_lane_weighted(img, thickness=5, alpha=0.8, beta=1, gamma=0): 在原始图像上绘制检测到的车道线,并添加曲率和车辆位置信息。
  11. process_image(img, reset=False): 对输入的图像进行处理,并返回带有检测到的车道线的图像。
  12. set_img_size(img_size): 设置图像的大小。

        这些函数共同构成了一个车道线检测算法,可以在道路图像中准确地检测出车道线并估计车辆的位置和行驶曲率。

2.4 主函数代码

import sys
import os
import json
import numpy as np
import cv2 as cv
from PIL import Image
import LaneFinder
import onnxruntime as rtlabels = ["person","bicycle", "car", "motorbike", "aeroplane","bus", "train", "truck", "boat", "traffic light","fire hydrant", "stop sign", "parking meter", "bench","bird", "cat", "dog", "horse", "sheep", "cow", "elephant","bear", "zebra", "giraffe", "backpack", "umbrella", "handbag","tie", "suitcase", "frisbee", "skis", "snowboard", "sports ball","kite", "baseball bat", "baseball glove", "skateboard", "surfboard","tennis racket", "bottle", "wine glass", "cup", "fork", "knife", "spoon","bowl", "banana", "apple", "sandwich", "orange", "broccoli", "carrot", "hot dog","pizza", "donut", "cake", "chair", "sofa", "potted plant", "bed", "dining table","toilet", "TV monitor", "laptop", "mouse", "remote", "keyboard", "cell phone","microwave", "oven", "toaster", "sink", "refrigerator", "book", "clock", "vase","scissors", "teddy bear", "hair drier", "toothbrush"]OUTPUT_DIR = '../out/'
MODEL_WIDTH = 608
MODEL_HEIGHT = 608
class_num = 80
stride_list = [32, 16, 8]
anchors_3 = np.array([[12, 16], [19, 36], [40, 28]]) / stride_list[2]
anchors_2 = np.array([[36, 75], [76, 55], [72, 146]]) / stride_list[1]
anchors_1 = np.array([[142, 110], [192, 243], [459, 401]]) / stride_list[0]
anchor_list = [anchors_1, anchors_2, anchors_3]
iou_threshold = 0.3
colors = [(255, 0, 0), (0, 255, 0), (0, 0, 255), (0, 255, 255), (255, 0, 255), (255, 255, 0)]def preprocess(frame):image = Image.fromarray(cv.cvtColor(frame, cv.COLOR_BGR2RGB))img_h = image.size[1]img_w = image.size[0]net_h = MODEL_HEIGHTnet_w = MODEL_WIDTHscale = min(float(net_w) / float(img_w), float(net_h) / float(img_h))new_w = int(img_w * scale)new_h = int(img_h * scale)shift_x = (net_w - new_w) // 2shift_y = (net_h - new_h) // 2shift_x_ratio = (net_w - new_w) / 2.0 / net_wshift_y_ratio = (net_h - new_h) / 2.0 / net_himage_ = image.resize((new_w, new_h))new_image = np.zeros((net_h, net_w, 3), np.uint8)new_image[shift_y: new_h + shift_y, shift_x: new_w + shift_x, :] = np.array(image_)new_image = new_image.astype(np.float32)new_image = new_image / 255print('new_image.shape', new_image.shape)new_image = new_image.transpose(2, 0, 1).copy().reshape(1, 3, 608, 608)return new_image, imagedef overlap(x1, x2, x3, x4):left = max(x1, x3)right = min(x2, x4)return right - leftdef cal_iou(box, truth):w = overlap(box[0], box[2], truth[0], truth[2])h = overlap(box[1], box[3], truth[1], truth[3])if w <= 0 or h <= 0:return 0inter_area = w * hunion_area = (box[2] - box[0]) * (box[3] - box[1]) + (truth[2] - truth[0]) * (truth[3] - truth[1]) - inter_areareturn inter_area * 1.0 / union_areadef apply_nms(all_boxes, thres):res = []for cls in range(class_num):cls_bboxes = all_boxes[cls]sorted_boxes = sorted(cls_bboxes, key=lambda d: d[5])[::-1]p = dict()for i in range(len(sorted_boxes)):if i in p:continuetruth = sorted_boxes[i]for j in range(i + 1, len(sorted_boxes)):if j in p:continuebox = sorted_boxes[j]iou = cal_iou(box, truth)if iou >= thres:p[j] = 1for i in range(len(sorted_boxes)):if i not in p:res.append(sorted_boxes[i])return resdef _sigmoid(x):return 1.0 / (1 + np.exp(-x))def decode_bbox(conv_output, anchors, img_w, img_h, x_scale, y_scale, shift_x_ratio, shift_y_ratio):print('conv_output.shape', conv_output.shape)_, _, h, w = conv_output.shape conv_output = conv_output.transpose(0, 2, 3, 1)pred = conv_output.reshape((h * w, 3, 5 + class_num))pred[..., 4:] = _sigmoid(pred[..., 4:])pred[..., 0] = (_sigmoid(pred[..., 0]) + np.tile(range(w), (3, h)).transpose((1, 0))) / wpred[..., 1] = (_sigmoid(pred[..., 1]) + np.tile(np.repeat(range(h), w), (3, 1)).transpose((1, 0))) / hpred[..., 2] = np.exp(pred[..., 2]) * anchors[:, 0:1].transpose((1, 0)) / wpred[..., 3] = np.exp(pred[..., 3]) * anchors[:, 1:2].transpose((1, 0)) / hbbox = np.zeros((h * w, 3, 4))bbox[..., 0] = np.maximum((pred[..., 0] - pred[..., 2] / 2.0 - shift_x_ratio) * x_scale * img_w, 0)  # x_minbbox[..., 1] = np.maximum((pred[..., 1] - pred[..., 3] / 2.0 - shift_y_ratio) * y_scale * img_h, 0)  # y_minbbox[..., 2] = np.minimum((pred[..., 0] + pred[..., 2] / 2.0 - shift_x_ratio) * x_scale * img_w, img_w)  # x_maxbbox[..., 3] = np.minimum((pred[..., 1] + pred[..., 3] / 2.0 - shift_y_ratio) * y_scale * img_h, img_h)  # y_maxpred[..., :4] = bboxpred = pred.reshape((-1, 5 + class_num))pred[:, 4] = pred[:, 4] * pred[:, 5:].max(1)pred[:, 5] = np.argmax(pred[:, 5:], axis=-1)    pred = pred[pred[:, 4] >= 0.2]print('pred[:, 5]', pred[:, 5])print('pred[:, 5] shape', pred[:, 5].shape)all_boxes = [[] for ix in range(class_num)]for ix in range(pred.shape[0]):box = [int(pred[ix, iy]) for iy in range(4)]box.append(int(pred[ix, 5]))box.append(pred[ix, 4])all_boxes[box[4] - 1].append(box)return all_boxesdef convert_labels(label_list):if isinstance(label_list, np.ndarray):label_list = label_list.tolist()label_names = [labels[int(index)] for index in label_list]return label_namesdef post_process(infer_output, origin_img):print("post process")result_return = dict()img_h = origin_img.size[1]img_w = origin_img.size[0]scale = min(float(MODEL_WIDTH) / float(img_w), float(MODEL_HEIGHT) / float(img_h))new_w = int(img_w * scale)new_h = int(img_h * scale)shift_x_ratio = (MODEL_WIDTH - new_w) / 2.0 / MODEL_WIDTHshift_y_ratio = (MODEL_HEIGHT - new_h) / 2.0 / MODEL_HEIGHTclass_number = len(labels)num_channel = 3 * (class_number + 5)x_scale = MODEL_WIDTH / float(new_w)y_scale = MODEL_HEIGHT / float(new_h)all_boxes = [[] for ix in range(class_number)]# print(infer_output[0].shape)# print(infer_output[1].shape)# print(infer_output[2].shape)for ix in range(3):    pred = infer_output[ix]print('pred.shape', pred.shape)anchors = anchor_list[ix]boxes = decode_bbox(pred, anchors, img_w, img_h, x_scale, y_scale, shift_x_ratio, shift_y_ratio)all_boxes = [all_boxes[iy] + boxes[iy] for iy in range(class_number)]print("all_box:", all_boxes)res = apply_nms(all_boxes, iou_threshold)print("res:", res)if not res:result_return['detection_classes'] = []result_return['detection_boxes'] = []result_return['detection_scores'] = []return result_returnelse:new_res = np.array(res)picked_boxes = new_res[:, 0:4]picked_boxes = picked_boxes[:, [1, 0, 3, 2]]picked_classes = convert_labels(new_res[:, 4])picked_score = new_res[:, 5]result_return['detection_classes'] = picked_classesresult_return['detection_boxes'] = picked_boxes.tolist()result_return['detection_scores'] = picked_score.tolist()return result_returndef preprocess_frame(bgr_img):bgr_img = bgr_img[:, :, ::-1]image = bgr_imgimage = LaneFinder.Image.fromarray(image.astype('uint8'), 'RGB')fframe = np.array(image)fframe = lf.process_image(fframe, False)frame = LaneFinder.Image.fromarray(fframe)framecv = cv.cvtColor(np.asarray(frame), cv.COLOR_RGB2BGR)return framecvdef calculate_position(bbox, transform_matrix, warped_size, pix_per_meter): if len(bbox) == 0:print('Nothing')else:point = np.array((bbox[1] / 2 + bbox[3] / 2, bbox[2])).reshape(1, 1, -1)pos = cv.perspectiveTransform(point, transform_matrix).reshape(-1, 1)return np.array((warped_size[1] - pos[1]) / pix_per_meter[1])def main():if (len(sys.argv) != 2):print("Please input video path")exit(1)frame_count = 0sess = rt.InferenceSession('../model/yolov4_bs.onnx')#open videovideo_path = sys.argv[1]print("open video ", video_path)cap = cv.VideoCapture(video_path)fps = cap.get(cv.CAP_PROP_FPS)Width = int(cap.get(cv.CAP_PROP_FRAME_WIDTH))Height = int(cap.get(cv.CAP_PROP_FRAME_HEIGHT))lf.set_img_size((Width, Height))#create output directoryif not os.path.exists(OUTPUT_DIR):os.mkdir(OUTPUT_DIR)output_Video = os.path.basename(video_path)output_Video = os.path.join(OUTPUT_DIR, output_Video)fourcc = cv.VideoWriter_fourcc(*'mp4v')  # DIVX, XVID, MJPG, X264, WMV1, WMV2outVideo = cv.VideoWriter(output_Video, fourcc, fps, (Width, Height))# 模型的输入和输出节点名,可以通过netron查看input_name = 'input'outputs_name = ['feature_map_1', 'feature_map_2', 'feature_map_3']# Read until video is completedwhile (cap.isOpened()):ret, frame = cap.read()if ret == True:#preprocessdata, orig = preprocess(frame)result_list = sess.run(outputs_name, {input_name: data})result_return = post_process(result_list, orig)frame_with_lane = preprocess_frame(frame)distance = np.zeros(shape=(len(result_return['detection_classes']), 1))for i in range(len(result_return['detection_classes'])):box = result_return['detection_boxes'][i]class_name = result_return['detection_classes'][i]# confidence = result_return['detection_scores'][i]distance[i] = calculate_position(bbox=box, transform_matrix=perspective_transform,warped_size=WARPED_SIZE, pix_per_meter=pixels_per_meter)label_dis = '{} {:.2f}m'.format('dis:', distance[i][0])cv.putText(frame_with_lane, label_dis, (int(box[1]) + 10, int(box[2]) + 15), cv.FONT_ITALIC, 0.6, colors[i % 6], 1)cv.rectangle(frame_with_lane, (int(box[1]), int(box[0])), (int(box[3]), int(box[2])), colors[i % 6])p3 = (max(int(box[1]), 15), max(int(box[0]), 15))out_label = class_namecv.putText(frame_with_lane, out_label, p3, cv.FONT_ITALIC, 0.6, colors[i % 6], 1)outVideo.write(frame_with_lane)print("FINISH PROCESSING FRAME: ", frame_count)frame_count += 1else:breakcap.release()outVideo.release()print("Execute end")if __name__ == '__main__':path = './configure.json'config_file = open(path, "rb")fileJson = json.load(config_file)cam_matrix = fileJson[0]["cam_matrix"]dist_coeffs = fileJson[0]["dist_coeffs"]perspective_transform = fileJson[0]["perspective_transform"]pixels_per_meter = fileJson[0]["pixels_per_meter"]WARPED_SIZE = fileJson[0]["WARPED_SIZE"]ORIGINAL_SIZE = fileJson[0]["ORIGINAL_SIZE"]cam_matrix = np.array(cam_matrix)dist_coeffs = np.array(dist_coeffs)perspective_transform = np.array(perspective_transform)pixels_per_meter = tuple(pixels_per_meter)WARPED_SIZE = tuple(WARPED_SIZE)ORIGINAL_SIZE = tuple(ORIGINAL_SIZE)lf = LaneFinder.LaneFinder(ORIGINAL_SIZE, WARPED_SIZE, cam_matrix, dist_coeffs,perspective_transform, pixels_per_meter)main()

3.结果视频

4. 结尾

        代码可以去资源下载,就这样吧

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

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

相关文章

绝地求生:PUBG延长GPU崩溃时间新方法

相信大家都在被GPU游戏崩溃苦恼已久&#xff0c;PUBG这个游戏崩溃&#xff0c;跟超频是没有多大关系的&#xff0c;只要超频TM5过测&#xff0c;YC过测&#xff0c;或者双烤过测&#xff0c;就没问题。主要是这个游戏的优化不行&#xff0c;特别40系显卡&#xff0c;对内存条也…

免费AI出图神器:StableStudio——定义AI作画新前景

StableStudio&#xff1a;探索艺术与科技的无限交界&#xff0c;StableStudio引领AI智能创作新浪潮&#xff01; - 精选真开源&#xff0c;释放新价值。 概览 ChatGPT大语言模型AI的诞生引爆了对AIGC的讨论。AIGC 又称生成式 AI (Generative AI)&#xff0c;是继专业生产内容&…

Python 全栈体系【四阶】(十七)

第五章 深度学习 一、基本理论 3. 深度神经网络训练法则 3.1 损失函数 3.1.1 什么是损失函数&#xff1f; 损失函数&#xff08;Loss Function&#xff09;&#xff0c;也有称之为代价函数&#xff08;Cost Function&#xff09;&#xff0c;用来度量预测值和实际值之间的差…

html5cssjs代码 029 CSS计数器

html5&css&js代码 029 CSS计数器 一、代码二、解释 该HTML代码定义了一个网页的结构和样式。在头部&#xff0c;通过CSS样式定义了body和h1-h2元素的样式。body元素的样式包括文本居中、计数器重置、字体颜色和背景颜色。h2元素的样式使用了CSS计数器来自动在标题前添加…

RIDE控制台中文显示为乱码问题解决方案【版本1.7.4.1】

1、方法&#xff1a; 将 C:\Users\user_name\AppData\Roaming\Python\Python37\site-packages\robotide\contrib\testrunner\testrunnerplugin.py文件中的第80行修改&#xff0c;改为utf-8 2、修改代码位置&#xff1a; 3、效果&#xff1a; 4、参考文章 试了前面的方法没有…

Python 深度学习第二版(GPT 重译)(一)

前言 序言 如果你拿起这本书&#xff0c;你可能已经意识到深度学习在最近对人工智能领域所代表的非凡进步。我们从几乎无法使用的计算机视觉和自然语言处理发展到了在你每天使用的产品中大规模部署的高性能系统。这一突然进步的后果几乎影响到了每一个行业。我们已经将深度学…

【C语言】结构体内存对齐问题

1.结构体内存对齐 我们已经基本掌握了结构体的使用了。那我们现在必须得知道结构体在内存中是如何存储的&#xff1f;内存是如何分配的&#xff1f;所以我们得知道如何计算结构体的大小&#xff1f;这就引出了我们今天所要探讨的内容&#xff1a;结构体内存对齐。 1.1 对齐规…

【Redis】Redis常见原理和数据结构

Redis 什么是redis redis是一款基于内存的k-v数据结构的非关系型数据库&#xff0c;读写速度非常快&#xff0c;常用于缓存&#xff0c;消息队列、分布式锁等场景。 redis的数据类型 string&#xff1a;字符串 缓存对象&#xff0c;分布式ID&#xff0c;token&#xff0c;se…

1236 - 二分查找

代码 #include<bits/stdc.h> using namespace std; int a[1100000]; int main() {int n,x,l,r,p,mid,i;cin>>n;for(i1;i<n;i)cin>>a[i];cin>>x;l1;rn;p-1;while(l<r){mid(rl)/2;if(a[mid]x){pmid;break;}else if(x<a[mid]) rmid-1;else if(x…

微软聘请了谷歌DeepMind的联合创始人

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

​HTTP与HTTPS:网络通信的安全卫士

✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天开心哦&#xff01;✨✨ &#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; ✨✨ 帅哥美女们&#xff0c;我们共同加油&#xff01;一起进步&am…

【高并发服务器 01】—— 基础知识回顾

接下来四周时间&#xff0c;我将会做一个高并发服务器相关的项目。 前置知识&#xff1a;操作系统系统编程、网络编程、基础的数据结构、C语言。 开发环境&#xff1a;VMware虚拟机&#xff1a;Ubuntu 20.04.6 LTS、vscode 今天先回顾一些基础知识。 1.文件与IO 标准IO&#…

Windows下hydra(海德拉/九头蛇)暴力猜解RDP的简单渗透实践

attscker machine&#xff1a;windows10 靶机&#xff1a;windoes server 2003 环境&#xff1a;网络可达 && mstsc开启 hydra字典&#xff1a; 123456 123admin admin123 123Com&#xff08;正确密码&#xff09; 进入hydra目录&#xff0c;字典与hydar.exe同一目录…

MySQL分组查询与子查询 + MySQL表的联结操作

目录 1 MySQL分组查询与子查询 1.1 数据分组查询 1.2 过滤分组 1.3 分组结果排序 1.4 select语句中子句的执行顺序 1.5 子查询 2 MySQL表的联结操作 2.1 关系表 2.2 表联结 2.3 笛卡尔积 2.4 内部联结 2.5 外联结 2.6 自联结 2.7 组合查询 1 MySQL分组查询与子查询…

Python 解析json文件 使用Plotly绘制地理散点图

目录 0、任务说明 1、解析json文件 2、使用Plotly绘制地理散点图 2.1 函数scatter_geo介绍 2.2 官方示例 3、根据json文件数据&#xff0c;准备绘制地理散点图的‘数据结构’ 4、完整代码及运行效果 0、任务说明 json文件中存放了关于地震的地理信息。 使用plotly模块…

Java柠檬班Java全栈自动化课程

Java柠檬班Java全栈自动化课程旨在教授学员Java编程技能与全栈开发知识&#xff0c;包括自动化测试、前端开发和后端开发。学员将学习如何构建完整的应用程序&#xff0c;并掌握自动化测试框架&#xff0c;为职业发展打下坚实基础。 课程大小&#xff1a;14G 课程下载&#x…

流畅的 Python 第二版(GPT 重译)(四)

第二部分&#xff1a;函数作为对象 第七章&#xff1a;函数作为一等对象 我从未认为 Python 受到函数式语言的重大影响&#xff0c;无论人们说什么或想什么。我更熟悉命令式语言&#xff0c;如 C 和 Algol 68&#xff0c;尽管我将函数作为一等对象&#xff0c;但我并不认为 Py…

爬虫基础:Web网页基础

爬虫基础&#xff1a;Web网页基础 前言Web网页基础网页的组成网页的结构节点树及节点间的关系选择器 前言 用浏览器访问不同的网站时&#xff0c;呈现的页面各不相同&#xff0c;你有没有想过为何会这样呢&#xff1f;了解一下网页的组成、结构和节点等内容。了解这些内容有助于…

挖掘网络宝藏:利用Scala和Fetch库下载Facebook网页内容

介绍 在数据驱动的世界里&#xff0c;网络爬虫技术是获取和分析网络信息的重要工具。本文将探讨如何使用Scala语言和Fetch库来下载Facebook网页内容。我们还将讨论如何通过代理IP技术绕过网络限制&#xff0c;以爬虫代理服务为例。 技术分析 Scala是一种多范式编程语言&…

用pdf2docx将PDF转换成word文档

pdf2docx是一个Python模块&#xff0c;可以将PDF文件转换为docx格式的Word文档。 pdf2docx模块基于Python的pdfminer和python-docx库开发&#xff0c;可以在Windows、Linux和Mac系统上运行。它可以从PDF文件中提取文本和图片&#xff0c;并将其转换成可编辑的Word文档&#xf…