OpenCV实现文档自动矫正(含源码和测试数据)

news2025/7/5 20:55:54

OpenCV实现文档自动矫正(含源码和测试数据)

目录

OpenCV实现文档自动矫正

1. OpenCV文档矫正的方法

(1)基于霍夫变换的文档矫正方法

(2)基于透视变换的文档矫正方法

2. OpenCV文档自动矫正实现

(0)项目说明

(1)基于霍夫变换的文档矫正方法(效果较差)

(2)基于透视变换的文档矫正方法(效果较好)

(3)文档矫正Android实现

3.项目源码下载


本篇,我们将基于OpenCV实现一个简易的文档自动矫正算法,支持通过用户交互实现文档矫正,也支持通过算法实现完全自动文档矫正,即文档一键矫正;使用用户交互时,需要用户使用鼠标标记图像中文档的四个角点的位置;该方法,不受背景图案影响,矫正精度取决于人工标记文档角点的精度;当然,也可以完全通过算法自动计算,实现真正的文档一键矫正

整套项目源码下载:OpenCV实现文档自动矫正

来,先看一下Demo矫正效果,小黑子又来啦~

通过用户交互实现文档矫正完全自动文档矫正(文档一键矫正

 【尊重原创,转载请注明出处】https://blog.csdn.net/guyuealian/article/details/128021906


1. OpenCV文档矫正的方法

(1)基于霍夫变换的文档矫正方法

对于普通的文档来说,文字排版一般是是一行一行,因此,如果能求得文档的倾斜角度,通过仿射变换就可实现文档旋正了;文档的倾斜角度可以通过霍夫变换计算。

优缺点:

  • 优点:算法实现比较简单,可完全自动化,无需人工调整位置;
  • 缺点:矫正效果比较差,容易受背景图案的影响,特别是当背景比较复杂时,矫正效果一塌糊涂。倾斜角度大于45°时,矫正方向容易反向;并且无法进行立体矫正;

(2)基于透视变换的文档矫正方法

透视变换在图像还原的上的应用很广泛,他是将成像投影到一个新的视平面。比如两个摄像头在不同的角度对统一物体进行拍照,物体上的同一个点在两张照片上的坐标是不一样的,为了实现两张图片同一个点的对应关系映射,透视变换就实现了此功能。

假设原图上某点P0,经过某种变换后(变换矩阵H)得到图像P1,那么P0和P1的对应关系可表示为:P0 = H * P1

如果,我们可以获得文档的四个角点位置P0(可以通过用户交互标记,也可以算法自动获得),并且已知并希望其矫正后的角点位置P1(文档是矩形的,矫正后的角点位置可以大致估计),利用P1和P0四个角点的对应关系,我们可以估计其变换矩阵H,再进行透视变换,其矫正效果会比直接使用霍夫变换的文档矫正方法要好很多。

优缺点:

  • 优点:算法实现也比较简单,不受倾斜角度大小影响,矫正效果贼好,应用场景比较广
  • 缺点:需要获得文档的四个角度位置,因而需要用户交互进行标记;文档的四个角点也可以通过图像处理或者深度学习去预测位置,可实现文档一键矫正

2. OpenCV文档自动矫正实现

(0)项目说明

```
.
├── data                        # 测试数据
│   ├── image1            
│   └── image2
├── utils                       # 项目相关算法工具
├── demo_correction_v1.py       # 基于霍夫变换的文档矫正方法(效果较差)
├── demo_correction_v2.py       # 基于透视变换的文档矫正方法(效果较好)
├── requirements.txt            # 项目依赖pythonb包,pip安装即可
├── LICENCE
└── README.md
```

项目代码需要用到pybaseutils工具,请使用pip安装即可;其他python包,请参考本人requirements.txt文件版本说明

pip install pybaseutils

(1)基于霍夫变换的文档矫正方法(效果较差)

 下图给出的是基于霍夫变换的文档矫正方法,其算法过程是:

  1. 对图像进行滤波平滑等处理,减少虚假边缘和噪声的影响;
  2. 先对图像进行Canny边缘检测
  3. 使用霍夫变换计算所有符合条件的线段
  4. 计算所有线段的倾斜角度,
  5. 将线段分为两类,大于45°作为纵向线段,用蓝色线段表示小于45°作为横向线段,用红色线段表示,如下图所示
  6. 由于只考虑倾斜角度小于45°时的文档矫正,因此只需要计算倾斜角度小于45°的线段的平均角度,即是文档的倾斜角度
  7. 对图像进行反向旋转,即可得到矫正后的文档图片

实现代码如下: 

# -*-coding: utf-8 -*-
"""
    @Author : PKing
    @E-mail : 390737991@qq.com
    @Date   : 2022-11-24 22:13:25
    @Brief  :
"""
import cv2
import numpy as np
from pybaseutils import geometry_tools, image_utils, file_utils


class ImageCorrection(object):
    """图像矫正程序"""

    @staticmethod
    def get_hough_lines(img: np.ndarray, rho=1, theta=np.pi / 180, threshold=100, max_angle=35, max_lines=50,
                        thickness=2, vis=False):
        """
        参考:https://blog.csdn.net/on2way/article/details/47028969
        :param img: 输入图像
        :param rho: 线段以像素为单位的距离精度,double类型的,推荐用1.0
        :param theta: 线段以弧度为单位的角度精度,推荐用numpy.pi/180
        :param threshold: : 累加平面的阈值参数,int类型,超过设定阈值才被检测出线段,
                            值越大,意味着检出的线段越长,检出的线段个数越少。根据情况推荐先用100试试
        :return:
        """
        if len(img.shape) == 3:
            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 灰度图像
        else:
            gray = img.copy()
        gray = image_utils.get_image_mask(gray, inv=True)
        edge = cv2.Canny(gray, threshold1=0, threshold2=255, apertureSize=3)
        # lines is (num_lines,1,2)==>(r,θ)==>(距离rho,角度theta)
        lines = cv2.HoughLines(edge, rho=rho, theta=theta, threshold=threshold)
        lines = [] if lines is None else lines[:, 0, :]
        lines = lines[0:min(len(lines), max_lines)]
        angles = []
        for i in range(len(lines)):
            rho, theta = lines[i]  # 其中theta是与Y轴的夹角
            angle = 90 - theta * (180 / np.pi)
            # print(rho, theta, angle)
            if abs(angle) < max_angle:  # 水平直线
                angles.append(angle)
                if vis:
                    # 该直线与第一列的交点
                    pt1 = (0, int(rho / np.sin(theta)))
                    # 该直线与最后一列的交点
                    pt2 = (edge.shape[1], int((rho - edge.shape[1] * np.cos(theta)) / np.sin(theta)))
                    # 绘制一条直线
                    cv2.line(img, pt1, pt2, (0, 0, 255), thickness=thickness)
            else:  # 垂直直线
                if vis:
                    #  (theta < (np.pi / 4.)) or (theta > (3. * np.pi / 4.0)) 垂直直线(<45°,>135°)
                    # 该直线与第一行的交点
                    pt1 = (int(rho / np.cos(theta)), 0)
                    # 该直线与最后一行的焦点
                    pt2 = (int((rho - edge.shape[0] * np.sin(theta)) / np.cos(theta)), edge.shape[0])
                    # 绘制一条白线
                    cv2.line(img, pt1, pt2, (255, 0, 0), thickness=thickness)
        angle = 0 if len(angles) < 1 else ImageCorrection.get_lines_mean_angle(angles)
        return angle, img

    @staticmethod
    def get_hough_lines_p(img: np.ndarray, rho=1, theta=np.pi / 180, threshold=100, max_angle=45,
                          max_lines=200, minLineLength=100, maxLineGap=10, thickness=2, vis=False):
        """
        https://blog.csdn.net/on2way/article/details/47028969
        :param img: 输入图像
        :param rho: 线段以像素为单位的距离精度,double类型的,推荐用1.0
        :param theta: 线段以弧度为单位的角度精度,推荐用numpy.pi/180
        :param threshold: : 累加平面的阈值参数,int类型,超过设定阈值才被检测出线段,
                            值越大,意味着检出的线段越长,检出的线段个数越少。根据情况推荐先用100试试
        :param  minLineLength 用来控制「接受直线的最小长度」的值,默认值为 0。
        :param  maxLineGap 用来控制接受共线线段之间的最小间隔,即在一条线中两点的最大间隔。
        :return:
        """
        if len(img.shape) == 3:
            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 灰度图像
        else:
            gray = img.copy()
        gray = image_utils.get_image_mask(gray, inv=True)
        edge = cv2.Canny(gray, threshold1=0, threshold2=255, apertureSize=3)
        lines = cv2.HoughLinesP(edge, rho=rho, theta=theta,
                                threshold=threshold,
                                minLineLength=minLineLength,
                                maxLineGap=maxLineGap)
        lines = [] if lines is None else lines[:, 0, :]
        lines = lines[0:min(len(lines), max_lines)]
        angles = []
        for x1, y1, x2, y2 in lines[:]:
            pt1, pt2 = (x1, y1), (x2, y2)  # P12 = point2-point1
            angle = geometry_tools.compute_horizontal_angle(pt1, pt2, minangle=False)
            # print(pt1, pt2, angle)
            if abs(angle) < max_angle:  # 水平直线
                angles.append(angle)
                if vis: cv2.line(img, pt1, pt2, color=(0, 0, 255), thickness=thickness)
            else:  # 垂直直线
                if vis: cv2.line(img, pt1, pt2, color=(255, 0, 0), thickness=thickness)
        angle = 0 if len(angles) < 1 else ImageCorrection.get_lines_mean_angle(angles)
        return angle, img

    @staticmethod
    def get_lines_mean_angle(angles):
        """求直线簇的平均角度"""
        angles = sorted(angles)
        r = len(angles) // 2
        ar = (r - r // 2, r + r // 2 + 1)
        angles = angles[ar[0]:ar[1]]
        angle = np.mean(angles)
        return angle

    @staticmethod
    def rotation(image, angle):
        """实现图像旋转"""
        h, w = image.shape[:2]
        center = (w / 2., h / 2.)
        mat = cv2.getRotationMatrix2D(center, angle, 1.0)
        image = cv2.warpAffine(image, mat, dsize=(w, h), borderMode=cv2.BORDER_CONSTANT, borderValue=(127, 127, 127))
        return image

    @staticmethod
    def correct(image, max_angle=35, vis=False):
        """
        图像矫正
        :param image: 输入RGB或BGR图像
        :param max_angle: 图像最大的倾斜角度,超过该角度的无法矫正,默认不超过35°
        :param vis: 是否可视化图像矫正结果
        :return: image返回矫正后的图像
        :return: angle返回原始图像倾斜的角度
        """
        # angle, image_line = ImageCorrection.get_hough_lines(image, max_angle=max_angle,vis=vis)
        angle, image_line = ImageCorrection.get_hough_lines_p(image, max_angle=max_angle, vis=vis)
        image = ImageCorrection.rotation(image, angle=-angle)  # 9ms
        if vis:
            print(angle)
            image_line = image_utils.resize_image(image_line, size=(None, image.shape[0]))
            image_line = np.hstack((image_line, image))
            image_utils.cv_show_image("Origin-Alignment", image_line, delay=0, use_rgb=False)
        return image, angle


def image_correction_demo(image_dir):
    """
    :param image_dir:
    :return:
    """
    image_list = file_utils.get_files_lists(image_dir)
    alignment = ImageCorrection()
    for image_file in image_list:
        print(image_file)
        image = cv2.imread(image_file)
        image, angle = alignment.correct(image, vis=True)
        print("倾斜角度:{}".format(angle))
        print("--" * 10)


if __name__ == "__main__":
    image_dir = "data/image1"  # 测试图片
    image_correction_demo(image_dir)

该方法,矫正效果比较差,容易受背景图案的影响,特别是当背景比较复杂时,矫正效果一塌糊涂。倾斜角度大于45°时,矫正方向容易反向;并且无法进行立体矫正:

(2)基于透视变换的文档矫正方法(效果较好)

如果,我们可以获得文档的四个角点位置P0(可以通过用户交互标记,也可以算法自动获得,即一键文档矫正),并且已知并希望其矫正后的角点位置P1(文档是矩形的,矫正后的角点位置可以大致估计),利用P1和P0四个角点的对应关系,我们可以估计其变换矩阵H,再进行透视变换,其矫正效果会比直接使用霍夫变换的文档矫正方法要好很多。

基于透视变换的文档矫正方法的实现过程如下:

  • 假设原图上某点P0,经过某种变换后(变换矩阵H)得到图像P1,那么P0和P1的对应关系可表示为:P0 = H * P1
  • 获得图像中文档的四个角点位置P0

① 如果选择用户交互获得,则use_mouse=True,这时需要使用鼠标标记图像中文档的四个角点的位置;该方法,不受背景图案影响,矫正精度取决于人工标记文档角点的精度

② 如果选择算法自动计算(文档一键矫正),则use_mouse=False,这时算法会通过图像处理自动获得文档角点的位置;该方法需要通过图像处理自动获得文档的四个角点,受背景图案影响较大。

  • 估计矫正后文档四个角点的位置P1

由于实际文档是矩形,其长宽比是固定的;基于这一先验信息,我们可以利用原始图像的四个角点位置P0,大致估计其矫正后四个角点的位置P1。

  • 使用OpenCV的getPerspectiveTransform()函数计算变换矩阵H
  • 使用OpenCV的warpPerspective()函数进行透视变换
  • 得到文档矫正后的图片

基于透视变换的文档矫正方法的关键代码如下:


def document_correct_by_mouse(image, winname="document_correct_by_mouse"):
    """
    通过鼠标操作获得文档的四个角点
    :param image: 输入图像
    :param winname: 窗口名称
    :return:
    """
    corners = np.zeros(shape=(0, 2), dtype=np.int32)
    mouse = mouse_utils.DrawImageMouse(max_point=4, thickness=5)
    image_utils.cv_show_image("correct-result", np.zeros_like(image) + 128, use_rgb=False, delay=1)
    while len(corners) < 4:
        corners = mouse.draw_image_polygon_on_mouse(image, winname=winname)
        corners = np.asarray(corners)
        if len(corners) < 4:
            mouse.clear()
            print("已经标记了文档的{}个角点,需要标记4个角点".format(len(corners)))
            cv2.waitKey(0)
    print("标记文档的4个角点={}".format(corners.tolist()))
    return corners


def document_correct_by_auto(image, winname="document_correct_by_auto", vis=False):
    """
    通过算法自动获得文档的四个角点
    :param image: 输入图像
    :param winname: 窗口名称
    :param vis: 是否可视化
    :return:
    """
    corners = corner_utils.get_document_corners(image)
    if vis:
        image = image_utils.draw_image_points_lines(image, corners, fontScale=2.0, thickness=5)
        image_utils.cv_show_image(winname, image, use_rgb=False)
    return corners


def document_correct_image_example(image, use_mouse=False, winname="document", vis=True):
    """
    通过算法自动获得文档的四个角点
    :param image: 输入图像
    :param use_mouse: True:通过鼠标操作获得文档的四个角点
                      False:通过算法自动获得文档的四个角点
    :param winname: 窗口名称
    :param vis: 可视化效果
    :return:
    """
    # 获得文档的四个角点
    if use_mouse:
        corners = document_correct_by_mouse(image, winname=winname)  # 通过鼠标操作获得文档的四个角点;
    else:
        corners = document_correct_by_auto(image)  # 通过算法自动获得文档的四个角点
    # 在原图显示角点
    image = image_utils.draw_image_points_lines(image, corners, circle_color=(0, 255, 0), fontScale=2.0, thickness=5)
    image_utils.cv_show_image(winname, image, use_rgb=False, delay=10)
    # 实现文档矫正
    document_image_correct(image, corners, vis=vis)


if __name__ == '__main__':
    image_dir = "data/image1"  # 测试图片
    use_mouse = True  # 是否通过鼠标操作获得文档的四个角点
    image_list = file_utils.get_files_lists(image_dir)
    for image_file in image_list:
        print(image_file)
        image = cv2.imread(image_file)
        document_correct_image_example(image, use_mouse=use_mouse)
    cv2.waitKey(0)

 整体而言,基于透视变换的文档矫正方法会比基于霍夫变换的文档矫正方法的矫正效果要好很多

 下面是Demo矫正效果

① 如果选择用户交互获得,则use_mouse=True,这时需要使用鼠标标记图像中文档的四个角点的位置;该方法,不受背景图案影响,矫正精度取决于人工标记文档角点的精度

② 如果选择算法自动计算(文档一键矫正),则use_mouse=False,这时算法会通过图像处理自动获得文档角点的位置;该方法需要通过图像处理自动获得文档的四个角点,受背景图案影响较大。

通过用户交互实现文档矫正完全自动文档矫正(文档一键矫正)

(3)文档矫正Android实现

目前,正计划实现Android版本的文档矫正Demo,如果你有这方面的技术需求,可以微信公众号联系哦


3.项目源码下载

整套项目源码下载:OpenCV实现文档自动矫正

  • 基于霍夫变换的文档矫正方法:demo_correction_v1.py
  • 基于透视变换的文档矫正方法: demo_correction_v2.py,设置use_mouse=True通过用户交互进行文档矫正;设置use_mouse=False,通过算法自动计算,实现文档一键矫正
  • 相关测试数据: 数据放在项目data目录下

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

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

相关文章

【Linux系统】第二篇、权限管理篇

文章目录一、Linux下的用户二、文件的权限1. 文件访问者的分类2. 文件类型和访问权限3. 文件权限值的表示方法三、文件访问权限的相关设置方法1. chmod2. chown3. chgrp4. umask&#xff08;重点&#xff09;四、file指令五、目录的权限粘滞位一、Linux下的用户 这里我们在上一…

从 12K 到 60K, 这 2023Java 研发必问高级面试题,过关斩将拿 offer

前言 学习某一门技术或者框架的时候&#xff0c;第一步当然是要了解下面这几样东西。 是什么&#xff1f; 有哪些特点&#xff1f; 有哪些应用场景&#xff1f; 有哪些成功使用的案例&#xff1f; … 为了让你更好地了解 Netty 以及它诞生的原因&#xff0c;先从传统的网…

R语言中实现马尔可夫链蒙特卡罗MCMC模型

什么是MCMC&#xff0c;什么时候使用它&#xff1f; MCMC只是一个从分布抽样的算法。 这只是众多算法之一。这个术语代表“马尔可夫链蒙特卡洛”&#xff0c;因为它是一种使用“马尔可夫链”&#xff08;我们将在后面讨论&#xff09;的“蒙特卡罗”&#xff08;即随机&#…

区间信息维护与查询【树状数组 】 - 原理1 一维树状数组

区间信息维护与查询【树状数组 】 - 原理1 一维树状数组 【原理1】 一维树状数组 有一个包含n 个数的数列2, 7, 1, 12, 5, 9 …&#xff0c;请计算前i 个数的和值&#xff0c;即前缀和sum[i ]a [1]a [2]…a [i ]&#xff08;i 1, 2, …, n&#xff09;。该怎么计算呢&#xf…

RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记

RISC-V入门 [完结] 循序渐进&#xff0c;学习开发一个RISC-V上的操作系统 - 汪辰 - 2021春 RISC-V 部分作业答案 参考 RISC-V ISA 基本介绍 历史简介 自由&#xff08;Free&#xff09;与开放&#xff08;Open&#xff09; RISC-V 念作 “risk-five”&#xff0c;代表着Berke…

JSON.stringify() / JSON.parse() / JSON 真是个好东西

目录 1. JSON 基本概念 1.1 JavaScript 对象表示法 1.2 JSON 文件 1.3 JSON 语法 2. XML VS JSON 2.1 共同点 2.2 不同点 2.3 使用步骤对比 3. JSON.parse() 3.1 使用介绍 3.2 使用 reviver 参数&#xff0c;将 JSON 对象解析出来的 string 转化为 Date 4. JSON.st…

【ML特征工程】第 4 章 :特征缩放的影响:从词袋到 Tf-Idf

&#x1f50e;大家好&#xff0c;我是Sonhhxg_柒&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f4dd;个人主页&#xff0d;Sonhhxg_柒的博客_CSDN博客 &#x1f4c3; &#x1f381;欢迎各位→点赞…

【知识网络分析】耦合网络(bibliographic coupling)

耦合网络(bibliographic coupling) 1 读取本地文献并构建耦合网络数据集2 网络数据集精简3 中心点附近网络子群绘制4 求解网络图中节点中心度相关指标数值1 读取本地文献并构建耦合网络数据集 新建一个notebook文件,第一步导入功能包和数据集,案例中仍使用2020-2021年WOS数…

第八章《Java高级语法》第2节:补码

在Java语言中,使用补码的形式来表示数字。补码是计算机表示数字的一种规则或者是表示形式,它的算法很简单:用最左边的一个二进制位表示数字的正负,0表示正数,用1表示负数,专业上把表示符号的这个二进制位叫做“符号位”。符号位后面剩余的二进制位表示数字本身。 对于正…

PyQt5 QWebEngineView网页交互

QWebEngineView网页交互QWebEngineView常用方法加载并显示外部的Web页面加载并显示本地的Web页面加载并显示嵌入的HTML代码QWebEngineView常用方法 方法描述load(QUrl url)加载指定的URL并显示setHtml(QString &html)将网页视图的内容设置为指定的HTML内容 核心代码&#…

chapter2——时钟和复位

目录1.同步设计2.推荐的设计技术3.时钟方案4.门控时钟方法学5.复位信号的设计策略6.控制时钟偏移1.同步设计 在同步设计中由单个主时钟和单个主置位/复位信号驱动设计中所有的时序器件&#xff0c;对于ASIC的时钟域控制最安全的方法就是同步设计。 避免使用行波计数器 由于第…

Python 实现自动化测试 dubbo 协议接口

前言 在工作或学习过程中&#xff0c;可能会遇到后端服务里有使用 dubbo 协议实现的接口&#xff0c;dubbo 协议接口的测试方法不同于 http/https 类型的接口&#xff0c;不能简单使用request.post的方法来完成自动化测试。 如果需要对 dubbo 协议的接口进行自动化测试&#…

FFmpeg进阶:生成视频的缩略图

文章目录1.读取对应位置的视频帧2.添加时间信息3.对图像进行拼接4.输出拼接图像5.显示效果很多时候为了方便预览视频内容&#xff0c;我们会随机的抽取视频当中的一些帧组成一个图片作为视频的缩略图。这里介绍一下如何通过FFmpeg生成视频的缩略图。其实原理很简单&#xff0c;…

MyBatis association解决多对一和collection解决一对多的映射关系

多对一的映射关系 创建Emp和Dept类 1.处理多对一映射关系方式一&#xff1a;级联属性赋值 2.处理多对一映射关系方式二&#xff1a;association实现 association:处理多对一的映射关系 property:需要处理多对的映射关系的属性名 javaType:该属性的类型 3.处理多对一映射关…

Metabase学习教程:视图-6

表格视图几乎可以来做所有的事情 了解如何设置条件格式、小条形图、值格式等。 表格是数据的自然栖息地&#xff0c;对应关系数据库列和对应的行记录。它们可能不像条形图或者地图&#xff0c;但当你在很多领域工作时&#xff0c;它们往往是你所需要的。Metabase中的表可视化…

运动品牌推荐:2022年最值得入手的一些运动装备

运动是一个比较枯燥的过程&#xff0c;不断的身体重复&#xff0c;会让运动者的注意力过度的关注到自己身体的疲惫感并且放大&#xff0c;这个时候我们就可以通过外在的运动装备来消除这些疲劳感&#xff0c;提高自己的运动积极性。不过哪些运动装备好用并适合自己呢&#xff1…

服务器配置怎么查看

服务器配置怎么查看 在我们找服务器商买服务器时&#xff0c;一般都是根据自己需求来选择需要什么配置的服务器。 选服务器时主要看CPU、内存、硬盘、带宽、这几个主要配置今天艾西就教你怎么查看服务器配置 CPU、内存怎么查看&#xff1a; 方法一&#xff1a;我们远程进入服…

学术Paper写作技巧要点讲解

在国外图书馆阅读他人的学术文章的时候&#xff0c;是否发现他们英文与你的不一样&#xff1f;虽然他们的Paper与你的有相似的结构&#xff0c;即开头、正文、结论&#xff0c;但是你的写作与他们的比起来还是显得简单多了。就是类似于国内毕业Paper的写作&#xff0c;在国外学…

断点续传小解

断点续传的原理 HTTP 协议是互联网上应用最广泛网络传输协议之一&#xff0c;它基于 TCP/IP 通信协议来传递数据。断点续传的奥秘就隐藏在这 HTTP 协议中了。 我们知道HTTP请求会有一个Request header 和 Response header&#xff0c;在请求头里边有个和Range相关的参数 当下…

6种交互式内容创意帮助跨境电商卖家提高独立站商店知名度

关键词&#xff1a;跨境电商卖家、独立站商店 交互式内容是一种允许用户与之交互的内容。一些示例包括在线投票、问答环节、交互式视频和交互式计算器等交互式工具。此内容类型允许查看者通过单击或拖动项目来自定义显示方式和内容。内容还可以引导读者采取您想要的操作&#x…