【python】OpenCV—Scanner

news2025/1/11 20:06:46

在这里插入图片描述

文章目录

  • 1、需求描述
  • 2、代码实现
  • 3、涉及到的库函数
    • cv2.arcLength
    • cv2.approxPolyDP
    • skimage.filters.threshold_local
    • imutils.grab_contours
  • 4、完整代码
  • 5、参考

1、需求描述

输入图片
在这里插入图片描述
扫描得到如下的结果
在这里插入图片描述

用OpenCV构建文档扫描仪只需三个简单步骤:

1.边缘检测
2.使用图像中的边缘来找到代表被扫描纸张的轮廓。
3.应用透视变换来获得文档的自顶向下视图。

2、代码实现

导入必要的包

from skimage.filters import threshold_local
import numpy as np
import argparse
import cv2
import imutils

初始化一个坐标列表,该列表中的第一个元素是左上,第二个元素是右上,第三个元素是右下,第四个元素是左下

该坐标排序方法有缺陷,具体可参考 【python】OpenCV—Coordinates Sorted Clockwise

def order_points(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_points(pts)
	(tl, tr, br, bl) = rect
	# 计算新图像的宽度,这将是右下角和左下角x坐标或右上角和左上角x坐标之间的最大距离
	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))
	# 计算新图像的高度,这将是右上角和右下角y坐标或左上角和左下角y坐标之间的最大距离
	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

构造参数解析器并解析参数

ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required = True, default="1.jpg",
				help = "Path to the image to be scanned")
args = vars(ap.parse_args())


# 加载图像并计算旧高度与新高度的比率,克隆它,并调整它的大小
image = cv2.imread(args["image"])


ratio = image.shape[0] / 500.0
orig = image.copy()
image = imutils.resize(image, height=500)

# ratio = 1.0
# orig = image.copy()


# 将图像转换为灰度,模糊它,并在图像中找到边缘
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv2.imwrite("gray.jpg", gray)

在这里插入图片描述

gray = cv2.GaussianBlur(gray, (7, 7), 0)
cv2.imwrite("GaussianBlur.jpg", gray)

这里 kernel size 需要设置大一些,不然很容易检测到发票上的黑色字体为轮廓了
在这里插入图片描述

edged = cv2.Canny(gray, 75, 200)
cv2.imwrite("Canny.jpg", edged)

在这里插入图片描述

显示原始图像和边缘检测图像

print("STEP 1: Edge Detection")
cv2.imshow("Image", image)
cv2.imshow("Edged", edged)
cv2.waitKey(0)

在这里插入图片描述

找到边缘图像中的轮廓,只保留最大的轮廓,并初始化屏幕轮廓

cnts = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
cnts = sorted(cnts, key = cv2.contourArea, reverse = True)[:5]
# 循环迭代所有轮廓
for c in cnts:
	# 近似轮廓
	peri = cv2.arcLength(c, True)
	approx = cv2.approxPolyDP(c, 0.02 * peri, True)
	# cv2.drawContours(image, c, -1, (0, 0, 255), 3)
	# 如果我们的近似轮廓有4个点,那么我们可以假设我们已经找到了我们的屏幕
	if len(approx) == 4:
		screenCnt = approx
		break

# 画出这张票的轮廓
print("STEP 2: Find contours of paper")
cv2.drawContours(image, [screenCnt], -1, (0, 255, 0), 2)
cv2.imshow("Outline", image)
cv2.waitKey(0)

在这里插入图片描述

# 应用四点变换获得原始图像自上而下的视图
warped = four_point_transform(orig, screenCnt.reshape(4, 2) * ratio)
cv2.imwrite("warped.jpg", warped)

在这里插入图片描述

# 将变换后的图像转换为灰度,然后使用阈值给它“黑白”纸的效果
warped = cv2.cvtColor(warped, cv2.COLOR_BGR2GRAY)
cv2.imwrite("warped_gray.jpg", warped)

在这里插入图片描述

T = threshold_local(warped, 11, offset = 10, method = "gaussian")
warped = (warped > T).astype("uint8") * 255
cv2.imwrite("warped_threshold_local.jpg", warped)

在这里插入图片描述

# 显示原始和扫描图像
print("STEP 3: Apply perspective transform")
cv2.imshow("Original", imutils.resize(orig, height = 650))
cv2.imshow("Scanned", imutils.resize(warped, height = 650))
cv2.waitKey(0)

在这里插入图片描述

再来个例子

输入图片
在这里插入图片描述
输出结果

在这里插入图片描述

3、涉及到的库函数

cv2.arcLength

cv2.arcLength 是 OpenCV(Open Source Computer Vision Library)库中的一个函数,用于计算多边形的周长或轮廓的弧长。这个函数在处理图像中的形状分析、轮廓检测等任务时非常有用。它接受一个轮廓(轮廓是一系列的点,通常通过边缘检测或轮廓查找算法获得)作为输入,并返回该轮廓的周长。

length = cv2.arcLength(contour, True)
  • contour:输入轮廓,应该是一个点集(通常是numpy.ndarray类型),这些点定义了轮廓的形状。

  • 第二个参数是True或False,指定轮廓是否应该被近似为闭合的(通过连接轮廓的第一个点和最后一个点)。如果轮廓已经是闭合的,或者你不关心轮廓是否闭合,可以传递True。如果轮廓不是闭合的,但你不希望它被视为闭合的,应该传递False。注意,这个参数在一些版本的OpenCV中可能不是必需的,或者默认值为True。

返回值

  • length:返回轮廓的周长或弧长,类型为浮点数。

cv2.approxPolyDP

cv2.approxPolyDP 是 OpenCV 库中的一个函数,用于对轮廓或曲线进行多边形逼近。该函数使用 Douglas-Peucker 算法来减少表示轮廓或曲线所需的点数,同时尽可能保持其形状特征。这个功能在图像处理、计算机视觉和机器学习等领域中非常有用,特别是在处理轮廓检测、形状分析等方面。

approx = cv2.approxPolyDP(curve, epsilon, closed)
  • curve:要逼近的曲线或轮廓,可以是二维点的列表或 NumPy 数组。
  • epsilon:逼近精度。这是一个距离值,表示原始曲线上的点与逼近后的多边形之间的最大距离。epsilon 的值越小,逼近结果越精确,但所需的点数也可能越多。
  • closed:一个布尔值,指定逼近后的多边形是否闭合。如果为 True,则逼近后的多边形是闭合的;如果为 False,则逼近结果可能不是闭合的。

返回值

  • approx:逼近后的多边形,以二维点的列表或 NumPy 数组的形式返回。

需要注意的是,epsilon 的值是一个权衡参数,需要根据具体应用进行调整。较小的 epsilon 值会产生更精确的逼近结果,但可能会增加计算复杂性和所需的存储空间。较大的 epsilon 值则会产生更简单的逼近结果,但可能会损失一些形状细节。因此,在实际应用中,需要根据具体需求来选择合适的 epsilon 值。

skimage.filters.threshold_local

skimage.filters.threshold_local 是 scikit-image 库中的一个函数,用于对图像进行局部阈值处理。与全局阈值处理(如使用固定的阈值来分割图像)不同,局部阈值处理会考虑图像中的每个像素及其邻域,从而根据局部区域的统计特性(如亮度或对比度)动态地确定阈值。这种方法在处理光照不均或具有不同亮度水平的图像时特别有用。

skimage.filters.threshold_local(image, block_size, method='gaussian', offset=0, mode='reflect', param=None, cval=0, **kwargs)

参数说明

  • image:输入图像,通常是灰度图像。
  • block_size:用于计算局部阈值的邻域大小(以像素为单位)。较大的块大小会增加计算成本,但可能会更好地适应图像中的光照变化。
  • method:确定如何计算局部阈值的方法。‘gaussian’(默认)使用高斯加权窗口,'mean’使用简单的平均值,'median’使用中位数。
  • offset:从计算出的局部阈值中减去的值。这可以用来调整最终的阈值水平。
  • mode:用于填充图像边界之外的值的方法。这可以是 ‘constant’、‘nearest’、‘reflect’ 或 ‘wrap’ 中的一个。
  • param:某些方法(如’gaussian’)可能接受额外的参数。对于 ‘gaussian’ 方法,param 可以是标准差(sigma),但在 threshold_local 函数中,这通常不是必需的,因为高斯权重是通过 block_size 隐式确定的。
  • cval:当 mode=‘constant’ 时,用于填充图像边界之外的值的常量值。
  • **kwargs:传递给 method 函数的额外关键字参数(如果有的话)。

返回值

  • 返回一个浮点数,表示计算出的局部阈值,或者一个与输入图像形状相同的数组,其中包含了每个像素的局部阈值(如果 method=‘multi’)。然而,注意 threshold_local 默认并不直接返回这样的数组;它返回的是一个单一的阈值,用于后续操作(如 threshold_local 通常会与 apply_threshold 或类似的函数结合使用来分割图像)。

imutils.grab_contours

imutils.grab_contours 是 imutils 库中的一个函数,该库是一个为OpenCV提供便利函数的Python库,旨在简化图像处理任务。在OpenCV中,特别是在使用cv2.findContours函数时,返回的轮廓信息可能会根据OpenCV的版本(主要是3.x和4.x版本之间)而有所不同。在OpenCV 3.x中,cv2.findContours返回三个值:图像、轮廓、和轮廓的层次结构。而在OpenCV 4.x中,它只返回两个值:轮廓和轮廓的层次结构。

imutils.grab_contours函数就是为了解决这种版本差异而设计的。它接受cv2.findContours的输出,并始终返回轮廓列表,无论OpenCV的版本如何。这样,开发者就可以编写不依赖于特定OpenCV版本的代码。

假设你正在使用OpenCV来检测图像中的轮廓,你可能会写出如下代码:

import cv2  
import imutils  
  
# 读取图像  
image = cv2.imread('path_to_image.jpg')  
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)  
blurred = cv2.GaussianBlur(gray, (5, 5), 0)  
edged = cv2.Canny(blurred, 30, 150)  
  
# 在OpenCV 4.x中,cv2.findContours返回两个值  
contours, hierarchy = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)  
  
# 使用imutils.grab_contours来获取轮廓  
contours = imutils.grab_contours(contours)  
  
# 现在,无论你的OpenCV版本是什么,contours都是一个轮廓列表  
# 接下来,你可以使用这些轮廓进行进一步的处理,比如绘制轮廓等  
cv2.drawContours(image, contours, -1, (0, 255, 0), 2)  
  
# 显示图像  
cv2.imshow("Image", image)  
cv2.waitKey(0)  
cv2.destroyAllWindows()

请注意,如果你正在使用OpenCV 3.x,cv2.findContours实际上会返回三个值,但imutils.grab_contours函数会忽略第一个返回值(通常是原始图像,但在这个上下文中并不重要),并只返回轮廓列表。这使得你的代码更加健壮,因为它不依赖于OpenCV的特定版本。

4、完整代码

# 导入必要的包
from skimage.filters import threshold_local
import numpy as np
import argparse
import cv2
import imutils

def order_points(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_points(pts)
	(tl, tr, br, bl) = rect
	# 计算新图像的宽度,这将是右下角和左下角x坐标或右上角和左上角x坐标之间的最大距离
	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))
	# 计算新图像的高度,这将是右上角和右下角y坐标或左上角和左下角y坐标之间的最大距离
	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


# 构造参数解析器并解析参数
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required = True, default="1.jpg",
				help = "Path to the image to be scanned")
args = vars(ap.parse_args())


# 加载图像并计算旧高度与新高度的比率,克隆它,并调整它的大小
image = cv2.imread(args["image"])


ratio = image.shape[0] / 500.0
orig = image.copy()
image = imutils.resize(image, height=500)

# ratio = 1.0
# orig = image.copy()


# 将图像转换为灰度,模糊它,并在图像中找到边缘
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv2.imwrite("gray.jpg", gray)

gray = cv2.GaussianBlur(gray, (7, 7), 0)
cv2.imwrite("GaussianBlur.jpg", gray)

edged = cv2.Canny(gray, 75, 200)
cv2.imwrite("Canny.jpg", edged)

# 显示原始图像和边缘检测图像
print("STEP 1: Edge Detection")
cv2.imshow("Image", image)
cv2.imshow("Edged", edged)
cv2.waitKey(0)

# 找到边缘图像中的轮廓,只保留最大的轮廓,并初始化屏幕轮廓
cnts = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
cnts = sorted(cnts, key = cv2.contourArea, reverse = True)[:5]
# 循环迭代所有轮廓
for c in cnts:
	# 近似轮廓
	peri = cv2.arcLength(c, True)
	approx = cv2.approxPolyDP(c, 0.02 * peri, True)
	# cv2.drawContours(image, c, -1, (0, 0, 255), 3)
	# 如果我们的近似轮廓有4个点,那么我们可以假设我们已经找到了我们的屏幕
	if len(approx) == 4:
		screenCnt = approx
		break

# 画出这张票的轮廓
print("STEP 2: Find contours of paper")
cv2.drawContours(image, [screenCnt], -1, (0, 255, 0), 2)
cv2.imshow("Outline", image)
cv2.waitKey(0)

# 应用四点变换获得原始图像自上而下的视图
warped = four_point_transform(orig, screenCnt.reshape(4, 2) * ratio)
cv2.imwrite("warped.jpg", warped)

# 将变换后的图像转换为灰度,然后使用阈值给它“黑白”纸的效果
warped = cv2.cvtColor(warped, cv2.COLOR_BGR2GRAY)
cv2.imwrite("warped_gray.jpg", warped)


T = threshold_local(warped, 11, offset = 10, method = "gaussian")
warped = (warped > T).astype("uint8") * 255
cv2.imwrite("warped_threshold_local.jpg", warped)

# 显示原始和扫描图像
print("STEP 3: Apply perspective transform")
cv2.imshow("Original", imutils.resize(orig, height = 650))
cv2.imshow("Scanned", imutils.resize(warped, height = 650))
cv2.waitKey(0)

5、参考

参考学习来自 imutils基础(4)构建一个文档扫描仪

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

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

相关文章

02线性表 - 链表

这里是只讲干货不讲废话的炽念,这个系列的文章是为了我自己以后复习数据结构而写,所以可能会用一种我自己能够听懂的方式来描述,不会像书本上那么枯燥和无聊,且全系列的代码均是可运行的代码,关键地方会给出注释^_^ 全…

windows edge自带的pdf分割工具(功能)

WPS分割pdf得会员,要充值!网上一顿乱找,发现最简单,最好用,免费的还是回到Windows。 Windows上直接在edge浏览器打开PDF,点击 打印 按钮,页面下选择对应页数 打印机 选择 另存为PDF,然后保存就…

memcached 高性能内存对象缓存

memcached 高性能内存对象缓存 memcache是一款开源的高性能分布式内存对象缓存系统,常用于做大型动态web服务器的中间件缓存。 mamcached做web服务的中间缓存示意图 当web服务器接收到请求需要处理动态页面元素时,通常要去数据库调用数据,但…

ProtoBuf的安装(win+ubuntu+centos版本)

Win下安装ProtoBuf教程 WProtoBuf Win版本 上方链接就是ProtoBuf官方在Github上面的仓库,我这里下的是21.11版本,至于你要下哪个版本,可以根据自己的需要去下载。 首先点击链接,进入首页,向下滑就可以找到ProtoBuf…

加密传输及相关安全验证:

1.1. 加密: 1.1.1. 对称加密: 特点:加解密用一个密钥,加解密效率高,速度快,有密钥交互的问题问题:双方如何交互对称密钥的问题,用非对称密钥的公钥加密对称密钥的混合加密方式常用…

IP溯源工具--IPTraceabilityTool

工具地址:xingyunsec/IPTraceabilityTool: 蓝队值守利器-IP溯源工具 (github.com) 工具介绍: 在攻防演练期间,对于值守人员,某些客户要求对攻击IP都进行分析溯源,发现攻击IP的时候,需要针对攻击IP进行分析…

PHP手边酒店多商户版平台小程序系统源码

🏨【旅行新宠】手边酒店多商户版小程序,一键解锁住宿新体验!🛌 🌈【开篇:旅行新伴侣,尽在掌握】🌈 还在为旅行中的住宿选择而纠结吗?是时候告别繁琐的搜索和比价过程&a…

js继承之构造函数继承

最近在看js红宝书,学到了继承这一章节,看到了下图这段代码根据自己理解不明白为什么两次实例的colors值不一样 又是自己画图又是查找资料看别人如何理解的,今天才按自己的理解搞明白为啥。可能我的理解也是有偏差错误的,希望佬可以…

开源防病毒工具--ClamAV

产品文档:简介 - ClamAV 文档 开源地址:Cisco-Talos/clamav:ClamAV - 文档在这里:https://docs.clamav.net (github.com) 一、引言 ClamAV(Clam AntiVirus)是一个开源的防病毒工具,广泛应用…

【Node.js】会话控制

express 中操作 cookie cookie 是保存在浏览器端的一小块数据。 cookie 是按照域名划分保存的。 浏览器向服务器发送请求时,会自动将 当前域名下可用的 cookie 设置在请求头中,然后传递给服务器。 这个请求头的名字也叫 cookie ,所以将 c…

NVidia 的 gpu 开源 Linux Kernel Module Driver 编译 安装 使用

见面礼,动态查看gpu使用情况,每隔2秒钟自动执行一次 nvidia-smi $ watch -n 2 nvidia-smi 1,找一台nv kmd列表中支持的 GPU 的电脑,安装ubuntu22.04 列表见 github of the kmd source code。 因为 cuda sdk 12.3支持最高到 ubu…

AMEYA360:思瑞浦推出汽车级理想二极管ORing控制器TPS65R01Q

聚焦高性能模拟芯片和嵌入式处理器的半导体供应商思瑞浦3PEAK(股票代码:688536)发布汽车级理想二极管ORing控制器TPS65R01Q。 TPS65R01Q拥有20mV正向调节功能,降低系统损耗。快速反向关断(Typ:0.39μs),在电池反向和各种汽车电气瞬…

OCR拍照识别采购单视频介绍

千呼新零售2.0系统是零售行业连锁店一体化收银系统,包括线下收银线上商城连锁店管理ERP管理商品管理供应商管理会员营销等功能为一体,线上线下数据全部打通。 适用于商超、便利店、水果、生鲜、母婴、服装、零食、百货、宠物等连锁店使用。 详细介绍请…

【BUG】已解决:NOAUTH Authentication required

已解决:NOAUTH Authentication required 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页,我是博主英杰,211科班出身,就职于医疗科技公司,热衷分享知识,武汉城市开发者社区主理人…

Atlas架构与原理

作者:楼高 一、总体架构 Atlas 是一个可伸缩且功能丰富的数据管理系统,深度集成了 Hadoop 大数据组件。简单理解就是一个 跟 Hadoop 关系紧密的,可以用来做元数据管理的一个系统,整个结构图如下所示: Atlas可以分为以下几层&…

IAR嵌入式开发解决方案已全面支持芯科集成CX3288系列车规RISC-V MCU,共同推动汽车高品质应用的安全开发

中国上海,2024年7月16日 — 全球领先的嵌入式系统开发软件解决方案供应商IAR与芯科集成电路(以下简称“芯科集成”)联合宣布,最新版本IAR Embedded Workbench for RISC-V 3.30.2功能安全版已全面支持芯科集成CX3288系列车规RISC-V…

【Node.js】初识 Node.js

Node.js 概念 Node.js 是一个开源与跨平台的 JavaScript运行时环境 ,在浏览器外运行 V8 JavaScript 引擎(Google Chrome的内核),利用事件驱动、非阻塞和异步输入输出 等技术提高性能。 可以理解为 Node.js就是一个服务器端的、非阻塞式 l/O 的、事件驱…

《Windows API每日一练》10.3 公用对话框

Windows最初发行时的主要目标之一就是提倡一种标准化的用户界面。对于公用菜单 项来说,这一目标实现得很快。几乎所有的软件制造商都采用了Alt-File-Open组合来打开 文件。但是,真正用来打开文件的对话框却经常很不一样。 从Windows 3.1开始&#xff0c…

排序系列 之 选择排序

!!!排序仅针对于数组哦本次排序是按照升序来的哦 介绍 快速排序英文名为SelectSort从数组中找到最小的放到前边 基本思路 1、默认待排序数组中第一个作为最小值2、找待排序数组(注意不是整个数组哦)中真正的最小值3…

使用LVS+NGinx+Netty实现数据接入

数据接入 链接参考文档 LVSKeepalived项目 车辆数据上收,TBox通过TCP协议连接到TSP平台 建立连接后进行数据上传。也可借由该连接实现远程控制等操作。 通过搭建 LV—NGinx—Netty实现高并发数据接入 LVS:四层负载均衡(位于内核层&#x…