OpenCV之信用卡识别实战

文章目录

  • 代码
  • 视频讲解
  • 模板匹配文件
  • 主程序(ocr_template_match.py)
  • myutils.py

代码

链接: https://pan.baidu.com/s/1KjdiqkyYGfHk97wwgF-j3g?pwd=hhkf 提取码: hhkf

视频讲解

模板匹配文件

请添加图片描述

主程序(ocr_template_match.py)

# 导入工具包
from imutils import contours # 从imutils库导入contours模块,imutils是一个方便的图像处理库,contours模块主要用于处理和操作图像轮廓。
import numpy as np
import argparse # 处理命令行参数
import cv2
import myutils

# 设置参数
ap = argparse.ArgumentParser() # 创建一个ArgumentParser对象,这个对象会保存命令行参数和程序文档等信息

# 添加一个命令行参数,参数的短名称是"-i",长名称是"--image",这个参数是必须的(required=True),参数的含义是输入图像的路径。
ap.add_argument("-i", "--image", required=True,
	help="path to input image")

# 添加另一个命令行参数,参数的短名称是"-t",长名称是"--template",这个参数也是必须的,参数的含义是模板图像的路径。
# 这个模板图像是OCR字体的图像,其中包含了0-9的数字。
ap.add_argument("-t", "--template", required=True,
	help="path to template OCR-A image")

# 解析命令行参数,然后将结果转为一个字典,字典的键是参数的名称,值是参数的值。
args = vars(ap.parse_args())
print('\n', args) # {'image': 'images/credit_card_03.png', 'template': 'images/ocr_a_reference.png'}

# 指定信用卡类型
"""
定义了一个名为FIRST_NUMBER的字典,这个字典中的键是信用卡卡号的首位数字的字符串形式,对应的值是信用卡的类型。
这是因为信用卡的种类可以通过卡号的首位数字来区分。
比如credit_card_03.png里面的信用卡的首位数字是5,那么这张信用卡就是MasterCard
"""
FIRST_NUMBER = {
	"3": "American Express",
	"4": "Visa",
	"5": "MasterCard",
	"6": "Discover Card"
}
# 绘图展示函数
def cv_show(name,img):
	cv2.imshow(name, img) # name: 窗口名称; img: 要展示的图像
	cv2.waitKey(0)
	cv2.destroyAllWindows()
	cv2.waitKey(1) # 对于MacOS系统,cv2.waitKey(1)是必要的,用于处理一些特定的系统事件,但在Windows上不一定需要。

# 读取一个模板图像。使用OpenCV的imread函数读取模板图像,图像的路径从前面解析的命令行参数args中获取。
img = cv2.imread(args["template"])
cv_show('img',img)

# 灰度图。将模板图像转换为灰度图像,使用OpenCV的cvtColor函数,COLOR_BGR2GRAY表示从BGR颜色空间转换到灰度空间。
ref = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv_show('ref',ref) # 展示灰度图像


"""
将灰度图像转换为二值图像,使用OpenCV的threshold函数。
10是阈值,255是当像素值高于(对于THRESH_BINARY_INV)或者小于(对于THRESH_BINARY)阈值时应给予的新的像素值 ,
THRESH_BINARY_INV表示进行反二进制阈值化(低于阈值的像素设置为255,高于阈值的像素设置为0)。
"""
ref = cv2.threshold(ref, 10, 255, cv2.THRESH_BINARY_INV)[1]
cv_show('ref',ref) # 展示二值图像


"""
计算轮廓
使用OpenCV的findContours函数计算二值图像的轮廓,RETR_EXTERNAL表示只检测外轮廓,CHAIN_APPROX_SIMPLE表示只保留终点坐标
返回的refCnts中每个元素都是图像中的一个轮廓
"""
refCnts, hierarchy = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
"""
cv2.drawContours函数用来在图像上画出找到的轮廓。第一个参数img是要绘制轮廓的图像,第二个参数refCnts是从前面找到的轮廓列表,
第三个参数-1表示要绘制所有轮廓,(0,0,255)是轮廓的颜色(这里是红色),3是线宽。
"""
cv2.drawContours(img, refCnts, -1, (0, 0, 255), 3)
cv_show('img', img)  # 展示绘制了轮廓的图像

"""
refCnts = myutils.sort_contours(refCnts, method="left-to-right")[0] 这行代码的主要作用是对模板图像中的轮廓进行排序。

在这里,myutils.sort_contours(refCnts, method="left-to-right")[0] 是调用 sort_contours 函数,对 refCnts(模板图像中的轮廓)进行排序。

method="left-to-right" 参数表示轮廓应该按照从左到右的顺序排序。这对于在图像中识别和分类对象(如数字)时非常有用,因为我们通常会按照从左到右的顺序进行阅读和识别。

sort_contours 函数的返回值是一个元组,其中包含两个元素:排序后的轮廓和对应的边界框。由于我们只关心排序后的轮廓,所以使用 [0] 索引来获取第一个元素,即排序后的轮廓。

因此,这行代码的结果 refCnts 是一个列表,其中的轮廓已经按照从左到右的顺序进行了排序。这样处理之后,程序可以按照这个顺序逐个处理和识别每个数字。
"""
refCnts = myutils.sort_contours(refCnts, method="left-to-right")[0] # 排序,从左到右,从上到下
# cv2.drawContours(img, refCnts, 0, (255, 0, 0), 2)
# cv_show('img', img) # 同学们这里可以修改method看看输出会有什么区别


digits = {} # 定义一个空字典digits,将用来存储每个数字模板。
# 遍历每一个轮廓
for (i, c) in enumerate(refCnts): # 遍历轮廓列表 refCnts。在每一次循环中,索引 i 和对应的轮廓 c 都被提取出来。
	# 计算外接矩形并且resize成合适大小
	(x, y, w, h) = cv2.boundingRect(c) # 对于每一个轮廓 c,计算其外接矩形,外接矩形的坐标被存储在 (x, y) 中,矩形的宽度和高度分别是 w 和 h
	# 使用刚刚计算出来的外接矩形的坐标和大小,从灰度图像 ref 中提取出对应的区域,这就是我们的 ROI。
	# 在OpenCV和很多其他图像处理库中,图像的索引方式通常为 [y:y+h, x:x+w],这样的顺序:先行(高度)后列(宽度)
	roi = ref[y:y + h, x:x + w]
	roi = cv2.resize(roi, (57, 88)) # 调整 ROI 的大小,使得每一个数字模板的大小都是一致的(这里是 57x88 像素)。

	# 将提取和调整大小后的 ROI 存储在字典 digits 中,字典的键是数字的索引 i,值是对应的数字模板。
	digits[i] = roi

# 初始化卷积核
# 这两行代码生成了两个矩形的结构元素(也称为卷积核或结构核),分别为9x3和5x5的大小。这些结构元素将在接下来的形态学操作中使用。
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))

#读取输入图像,预处理
image = cv2.imread(args["image"])
cv_show('image',image)
image = myutils.resize(image, width=300) # 用自定义的resize函数将图像的宽度缩放到300像素,高度按比例缩放
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 将缩放后的彩色图像转换为灰度图像。在OpenCV中,颜色图像通常是BGR格式,而在很多图像处理任务中,我们只需要灰度图像。
cv_show('gray',gray)


# 礼帽操作:原始输入-开运算(先腐蚀,在膨胀)的结果,突出更明亮的区域;黑帽操作常常用于强调比周围暗的区域,或者是强调比较小,且比周围区域暗的细节部分。
# 这行代码对灰度图像执行了一个名为"礼帽"的形态学操作,这是通过使用前面定义的9x3的矩形卷积核对图像进行开运算,然后从原始图像中减去结果来实现的
tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel) 
cv_show('tophat',tophat)

# 这行代码对进行了"礼帽"操作的图像应用Sobel算子,以计算图像的x方向(水平方向)的梯度。
# 这里ksize=-1表示使用3x3的Sobel滤波器。
gradX = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0, #ksize=-1相当于用3*3的
	ksize=-1)
gradX = np.absolute(gradX) # 这行代码将Sobel梯度的值转换为绝对值,因为梯度值可能是正值也可能是负值,转换为绝对值使所有梯度值都为正,方便后续处理。
"""
这三行代码对梯度图像进行了归一化和量化处理。
首先找到梯度图像的最小值和最大值,然后将梯度图像的亮度值线性映射到0-255的范围,以便显示和保存。
最后,将浮点型的梯度图像转换为8位无符号整型。
"""
(minVal, maxVal) = (np.min(gradX), np.max(gradX))
gradX = (255 * ((gradX - minVal) / (maxVal - minVal)))
gradX = gradX.astype("uint8")
print (np.array(gradX).shape) # 这行代码打印出处理后的梯度图像的形状(尺寸),包括高度和宽度。
cv_show('gradX',gradX)

# 通过闭操作(先膨胀,再腐蚀)将数字连在一起
# 使用闭操作来连结图像中的数字。闭操作是先进行膨胀,然后进行腐蚀。这样可以将断开的部分连在一起,主要用来关闭前景物体内部的小孔或者小黑点。这里使用的是前面定义的rectKernel,一个长方形的结构元素。
gradX = cv2.morphologyEx(gradX, cv2.MORPH_CLOSE, rectKernel) 
cv_show('gradX',gradX)

"""
使用阈值化操作来将图像转换为二值图像。其中,cv2.THRESH_BINARY表示大于阈值的像素点设为最大值(这里是255),小于阈值的设为0;
cv2.THRESH_OTSU表示使用Otsu算法来自动确定最优阈值,该方法适合对于双峰图像,需要将阈值参数设置为0。
输出的thresh是一个二值化后的图像。
"""
thresh = cv2.threshold(gradX, 0, 255,
	cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] 
cv_show('thresh',thresh)

# 再进行一次闭操作。这次使用的结构元素是sqKernel,一个正方形的结构元素,其目的是使得图像中的数字更加紧密,减小数字间的空隙。
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel) #再来一个闭操作
cv_show('thresh',thresh)


"""
计算图像的轮廓。cv2.findContours函数用来寻找二值图像中的轮廓。
这里使用的是cv2.RETR_EXTERNAL模式,表示只查找最外层的轮廓;cv2.CHAIN_APPROX_SIMPLE表示轮廓的存储方式,这种方式会存储轮廓的转折点,从而节省内存空间。
thresh.copy()是为了保护原图,因为findContours函数会修改输入的图像。
"""
threshCnts, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = threshCnts
# 在原图上画出找到的轮廓。
# cv2.drawContours函数用来在图像上画出轮廓,-1表示画出所有轮廓,(0,0,255)是轮廓的颜色,这里是红色,3是轮廓的宽度。
cur_img = image.copy()
cv2.drawContours(cur_img,cnts,-1,(0,0,255),3) 
cv_show('img',cur_img)


# 下面这段代码的主要目的是从所有找到的轮廓中筛选出数字的轮廓,并将这些轮廓按照从左到右的顺序排序。
locs = [] # 初始化一个空列表 locs,用于存储选出来的轮廓。
# 遍历找到的所有轮廓
for (i, c) in enumerate(cnts):
	# 对于每个轮廓,先计算其外接矩形,得到矩形的坐标 (x, y) 和宽高 (w, h)
	(x, y, w, h) = cv2.boundingRect(c)

	"""
	计算矩形的长宽比(宽度除以高度)。如果一个轮廓的外接矩形的长宽比在一定范围内(比如这里的2.5到4.0),
	并且宽度和高度也在一定范围内(比如这里的宽度40到55,高度10到20),就认为这个轮廓可能是一个数字的轮廓,将其保存到 locs 中。
	"""
	ar = w / float(h)
	# 选择合适的区域,根据实际任务来,这里的基本都是四个数字一组
	if ar > 2.5 and ar < 4.0:
		if (w > 40 and w < 55) and (h > 10 and h < 20):
			# 符合的留下来
			locs.append((x, y, w, h))
			# 展示此时的结果
			clone = image.copy()
			cv2.rectangle(clone, (x, y), (x + w, y + h), (0, 255, 0), 2)
			cv_show("Selection", clone)

# 对 locs 中的轮廓按照 x 坐标(即从左到右)进行排序。
locs = sorted(locs, key=lambda x:x[0]) # locs是一个包含4个元素的列表
# 展示排序后的结果
for (i, (gX, gY, gW, gH)) in enumerate(locs):
	clone = image.copy()
	cv2.rectangle(clone, (gX, gY), (gX + gW, gY + gH), (0, 255, 0), 2)
	cv_show("Sorted", clone)



# 这段代码的目的是遍历存储的符合条件的轮廓(即信用卡上的数字组),对每一组数字进行处理以识别出其中的数字,并将识别的数字显示在图像上。
output = []
# 遍历 locs 列表,它包含了一系列的轮廓(或者说是数字组)。每个轮廓都有自己的坐标 (gX, gY) 和尺寸 (gW, gH)
for (i, (gX, gY, gW, gH)) in enumerate(locs):
	# 初始化一个空列表 groupOutput,用于保存当前轮廓(数字组)内识别出的数字
	groupOutput = []

	# 这一行是从灰度图像中切出当前轮廓(数字组)的部分。稍微比轮廓大一些,是为了尽量避免切割到数字。
	group = gray[gY - 5:gY + gH + 5, gX - 5:gX + gW + 5]
	cv_show('group',group)
	# 对切出的图像进行二值化处理。二值化可以使得后续的轮廓检测操作更准确。
	group = cv2.threshold(group, 0, 255,
		cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
	cv_show('group',group)

	# 在二值化后的图像中寻找轮廓,这个轮廓就对应于信用卡上的每一个数字。
	digitCnts, hierarchy = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
	# 将找到的轮廓(即数字)按照从左到右的顺序进行排序,以确保我们识别出的信用卡号是按照正确的顺序。
	digitCnts = contours.sort_contours(digitCnts,
		method="left-to-right")[0]

	# 遍历每一个数字的轮廓
	for c in digitCnts:
		# 找到当前数值的轮廓,resize成合适的的大小
		(x, y, w, h) = cv2.boundingRect(c)
		# 从数字组图像中切出当前数字的部分
		roi = group[y:y + h, x:x + w]
		# 将切出的数字图像的尺寸调整为57x88,这是为了和模板的尺寸一致,从而可以进行模板匹配。
		roi = cv2.resize(roi, (57, 88))
		cv_show('roi',roi)

		# 计算匹配得分
		scores = []
		# 遍历每一个数字模板
		for (digit, digitROI) in digits.items():
			# 将切出的数字图像和模板进行匹配,得到的结果是一个矩阵,每一个元素是对应位置上的匹配得分。
			result = cv2.matchTemplate(roi, digitROI,
				cv2.TM_CCOEFF)
			# 找到匹配得分矩阵中的最大值和最小值,以及它们的位置。score 是最大匹配得分
			(_, score, _, _) = cv2.minMaxLoc(result)
			scores.append(score) # 将当前模板的匹配得分添加到得分列表中

		# 在所有模板的匹配得分中找出最大的那个,最大得分对应的数字就是识别出来的数字。将识别出来的数字添加到当前数字组的输出列表中。
		groupOutput.append(str(np.argmax(scores)))

	"""
	这两行代码的主要目的是在原始图像上标记出识别的数字,并且显示识别结果。
	
	1. cv2.rectangle(image, (gX - 5, gY - 5), (gX + gW + 5, gY + gH + 5), (0, 0, 255), 1):
	这行代码用于在原始图像上画出一个矩形,表示每一组的数字区域。cv2.rectangle()函数的参数是:图像、左上角坐标、右下角坐标、颜色和线条宽度。
	(gX - 5, gY - 5) 和 (gX + gW + 5, gY + gH + 5) 分别是矩形的左上角和右下角坐标,
	(0, 0, 255) 是矩形的颜色(这里是红色,因为在OpenCV中,颜色是以BGR而非RGB表示的),1 是线条的宽度。
	
	2. cv2.putText(image, "".join(groupOutput), (gX, gY - 15), cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2): 
	这行代码是将识别的数字添加到图像上。cv2.putText()函数的参数是:图像、文本、位置、字体、字体大小、颜色和线条宽度。
	"".join(groupOutput) 是要添加的文本,它是将每一组识别的数字拼接成一个字符串;(gX, gY - 15) 是文本的位置,这里选择的是每一组数字的左上角稍微向上一点的位置;
	cv2.FONT_HERSHEY_SIMPLEX 是字体;0.65 是字体的大小;(0, 0, 255) 是字体的颜色(这里选择的也是红色);2 是线条的宽度。
	"""
	cv2.rectangle(image, (gX - 5, gY - 5),
		(gX + gW + 5, gY + gH + 5), (0, 0, 255), 1)
	cv2.putText(image, "".join(groupOutput), (gX, gY - 15),
		cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2)

	# 将识别出来的数字添加到最终的输出列表中
	output.extend(groupOutput)

# 打印出识别出来的信用卡类型和信用卡号。这里 FIRST_NUMBER 是一个字典,它将信用卡号的第一位数字映射到对应的信用卡类型。
print("Credit Card Type: {}".format(FIRST_NUMBER[output[0]]))
print("Credit Card #: {}".format("".join(output)))
cv_show("Image", image) # 显示处理后的图像,可以看到识别出的信用卡号已经被画在了原图上。

myutils.py

import cv2

"""
sort_contours函数的作用是对轮廓和其外接矩形进行排序。

让我们一步一步地解读:

- zip(cnts, boundingBoxes)将轮廓列表cnts和外接矩形列表boundingBoxes合并成一个列表,其中每个元素都是一个由轮廓和其外接矩形组成的元组。

- sorted(zip(cnts, boundingBoxes), key=lambda b: b[1][i], reverse=reverse)使用Python的sorted函数对上述列表进行排序。
    - 排序的关键是key=lambda b: b[1][i],其中b是列表中的一个元素,也就是一个由轮廓和其外接矩形组成的元组,b[1]是外接矩形,b[1][i]是外接矩形的x或y坐标,取决于i的值。
    - 如果reverse=True,则对结果进行反向排序,即从大到小排序;如果reverse=False,则直接按照从小到大的顺序排序。

- zip(*sorted(zip(cnts, boundingBoxes), key=lambda b: b[1][i], reverse=reverse))使用zip函数和*操作符将排序后的列表转换回原来的形式,即分别是轮廓列表和外接矩形列表。

- (cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes), key=lambda b: b[1][i], reverse=reverse))将返回的轮廓列表和外接矩形列表分别赋值给cnts和boundingBoxes。

所以,这行代码的总体作用是按照x或y坐标对轮廓和其外接矩形进行排序,然后返回排序后的轮廓列表和外接矩形列表。

怕同学们忘记,补充一个点:
cv2.boundingRect()函数是一个计算轮廓外接矩形的函数,它的参数是一个轮廓。

该函数返回一个表示外接矩形的四元组,这四个值分别为(x, y, w, h)。

- x 和 y 表示外接矩形左上角点的坐标。
- w 和 h 分别表示外接矩形的宽度和高度。

"""
def sort_contours(cnts, method="left-to-right"):
    reverse = False
    i = 0

    if method == "right-to-left" or method == "bottom-to-top":
        reverse = True

    if method == "top-to-bottom" or method == "bottom-to-top":
        i = 1
    boundingBoxes = [cv2.boundingRect(c) for c in cnts] #用一个最小的矩形,把找到的形状包起来x,y,h,w
    (cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes),
                                        key=lambda b: b[1][i], reverse=reverse))

    return cnts, boundingBoxes



"""
resize函数用于调整图像的大小

下面这段代码定义了一个叫做resize的函数,这个函数接收一个图像和一个宽度或高度的值(或两者都有),并将这个图像缩放到指定的尺寸。

如果没有给出宽度和高度值,那么函数将返回原图。插值方法默认为cv2.INTER_AREA。
"""
def resize(image, width=None, height=None, inter=cv2.INTER_AREA):
    dim = None # 这里初始化了一个变量dim,它将在后续的代码中被用作储存图像新的尺寸
    (h, w) = image.shape[:2] # 这里从输入图像的形状(即尺寸)中提取出图像的高度(h)和宽度(w)
    if width is None and height is None: # 这一行代码检查了是否给出了新的宽度或高度值。如果没有给出,那么函数将直接返回原图
        return image

    # 如果没有指定新的宽度,那么我们就需要按照新的高度值来缩放图像。计算新的宽度值的方法是通过新的高度值与原高度的比例来计算新的宽度。
    if width is None:
        r = height / float(h)
        dim = (int(w * r), height)
    else: #  如果给出了新的宽度值,那么我们就需要按照新的宽度值来缩放图像。计算新的高度值的方法是通过新的宽度值与原宽度的比例来计算新的高度。
        r = width / float(w)
        dim = (width, int(h * r))
    """
    这段if-else代码的作用是在保持图像原有长宽比(即纵横比)的条件下,根据给出的新的宽度或高度来计算出新的图像尺寸。

    例子1:如果只给出了新的高度值 height(即 width 是 None),那么我们需要计算出新的宽度值,方法是:
    首先,我们计算出新的高度值与原高度 h 的比例 r = height / float(h)。
    然后,我们根据这个比例来计算新的宽度值 w * r,并将其转化为整数(因为像素值必须是整数)。
    最后,我们得到新的图像尺寸 dim = (int(w * r), height)。
    
    例子2:如果给出了新的宽度值 width,那么我们需要计算出新的高度值,方法是:
    首先,我们计算出新的宽度值与原宽度 w 的比例 r = width / float(w)。
    然后,我们根据这个比例来计算新的高度值 h * r,并将其转化为整数。
    最后,我们得到新的图像尺寸 dim = (width, int(h * r))。
    这样做的好处是可以保证图像在缩放过程中不会发生扭曲,也就是保持了图像的原有长宽比。
    """

    # 调用cv2.resize函数,将图像缩放到新的尺寸
    resized = cv2.resize(image, dim, interpolation=inter)
    """
    cv2.resize()是OpenCV库中的一个函数,用于调整图像的大小。

    该函数接收三个参数:
    
    - 第一个参数 image:需要调整大小的原始图像。
    - 第二个参数 dim:一个包含两个元素的元组,表示新的图像大小。这个大小是你要调整到的目标大小。
    - 第三个参数 interpolation:插值方法,也就是当我们调整图像大小时,如何计算新的像素值。 cv2.INTER_AREA 是一种插值方法,适合于图像缩小。OpenCV还提供了其他插值方法,如 cv2.INTER_LINEAR(线性插值,也是默认方法)、cv2.INTER_CUBIC(立方插值)等,这些方法在图像放大时可能会有较好的效果。
    
    函数的返回值 resized 就是调整大小后的新图像。这个新图像的大小就是 dim 所指定的大小。
    
    举个例子,如果你有一个大小为1000x1000的图像,你希望将它缩小到500x500,那么你可以这样调用这个函数:
    
    resized_image = cv2.resize(original_image, (500, 500), interpolation=cv2.INTER_AREA)
    
    在这个例子中,resized_image 就是大小为500x500的新图像。
    """

    return resized # 返回缩放后的图像

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

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

相关文章

【LeetCode】236.二叉树的最近公共祖先

题目 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个节点 p、q&#xff0c;最近公共祖先表示为一个节点 x&#xff0c;满足 x 是 p、q 的祖先且 x 的深度尽可能大&#xff08;一个节点也可以是它…

刷了3个月的华为OD算法题,总结了270多道,谈谈自己的感悟

目录 一、考研二战&#xff0c;入职华为&#xff0c;反向调剂电子科大深圳二、题目描述三、输入描述四、输出描述五、解题思路六、Java算法源码七、效果展示1、输入2、输出3、说明 大家好&#xff0c;我是哪吒。 最近一直在刷华为OD机试的算法题&#xff0c;坚持一天三道题的节…

STL空间配置器入门

STL简介   STL&#xff08;Standard Template Library&#xff0c;标准模板库&#xff09;&#xff0c;从根本上说&#xff0c;STL是一些“容器”的集合&#xff0c;这些“容器”有list,vector,set,map等&#xff0c;STL也是算法和其他一些组件的集合。 谈及组件&#xff0c…

如何在免费版 pycharm 中使用 github copilot (chatGPT)?

起因 在 vscode 中使用了 github copilot 以后&#xff0c;感觉这个人工智能还不错。 但 vscode 对于 python 项目调试并不是特别方便&#xff0c;所以想在 Pycharm 中也能使用同一个 github 账号&#xff0c;用上 copilot 的功能。 不需要等待&#xff0c;安装即用&#xff…

C++和Lua交互总结

C和Lua交互总结 Chapter1. C和Lua交互总结一、Lua与C的交互机制——Lua堆栈二、堆栈的操作三、C 调用 Lua1&#xff09;C获取Lua值2&#xff09;C调用Lua函数示例&#xff1a; 四、Lua 调用 C包装C函数 最后总结一下 Chapter1. C和Lua交互总结 原文链接&#xff1a;https://bl…

数据结构: 线性表(带头双向循环链表实现)

之前一章学习了单链表的相关操作, 但是单链表的限制却很多, 比如不能倒序扫描链表, 解决方法是在数据结构上附加一个域, 使它包含指向前一个单元的指针即可. 那么怎么定义数据结构呢? 首先我们先了解以下链表的分类 1. 链表的分类 链表的结构非常多样, 以下情况组合起来就有…

【图论】无向图连通性(tarjan算法)

割边&#xff1a;dfn[u]<low[v] 割点&#xff1a;dfn[u]<low[v] (若为根节点&#xff0c;要有两个v这样的点) 一.知识点&#xff1a; 1.连通&#xff1a; 在图论中&#xff0c;连通性是指一个无向图中的任意两个顶点之间存在路径。如果对于图中的任意两个顶点 u 和 v&…

06 HTTP(下)

06 HTTP&#xff08;下&#xff09; 介绍服务器如何响应请求报文&#xff0c;并将该报文发送给浏览器端。介绍一些基础API&#xff0c;然后结合流程图和代码对服务器响应请求报文进行详解。 基础API部分&#xff0c;介绍stat、mmap、iovec、writev。 流程图部分&#xff0c;描…

写材料使用恰当的词汇和专业术语,不要使用生僻或不恰当的词汇

注意使用恰当的词汇和专业术语是公文写作中的关键&#xff0c;不要使用过于生僻或不恰当的词汇。 首先&#xff0c;在选择词汇和专业术语时&#xff0c;需要了解公文所涉及的领域和专业知识。对于不同领域和专业的公文&#xff0c;需要选择恰当的词汇和术语&#xff0c;以确保公…

Akuity Certified ArgoCD课程学习与认证

今天是「DevOps云学堂」与你共同进步的第 48天 第⑦期DevOps实战训练营 7月15日已开营 实践环境升级基于K8s和ArgoCD 本文主要分享&#xff0c;如何免费地参与由Akuity Academy提供的ArgoCD GitOps 培训课程并取得认证证书。 目前Akuity Academy只发布了Introduction to Contin…

王道计网 第四章笔记

4.1 生活在网络层的“工人”是路由器,他负责各种异构网络的连接,但是因为他只生活在前三层所以从网络层之上的东西他不能管理,所以网路层之上的数据对于路由器来说必须是相同的、透明的。 常见的网络层协议有IP 和 ICMPTCP IP传输层协议FTP应用层协议一句话区分IP和MAC地址…

GO语言的垃圾回收机制

内存垃圾的产生 程序在内存上被分为堆区、栈区、全局数据区、代码段、数据区五个部分。对于C等早期编程语言栈上的内存回由编译器负责管理回收&#xff0c;而堆上的内存空间需要编程人员负责申请和释放。在Go中栈上内存仍由编译器负责管理回收&#xff0c;而堆上的内存由编译器…

snap xxx has “install-snap“ change in progress

error description * 系重复安装&#xff0c;进程冲突 solution 展示snap的改变 然后sudo snap abort 22即可终止该进程 之后重新运行install command&#xff5e;&#xff5e; PS: ubuntu有时候加载不出来&#xff0c;执行resolvectl flush-caches&#xff0c;清除dns缓存…

ChatGPT即将取代程序员

W...Y的主页 相信ChatGPT大家已经都不陌生&#xff0c;我们经常会在工作和学习中应用。但是ChatGPT的发展速度飞快。功能也越来越全面。ChatGPT的文章也是层次不穷的出现&#xff0c;ChatGPT即将取代程序员的消息也铺天盖地。那ChatGPT真的会取代程序员吗&#xff1f;我们是否…

【深度学习_TensorFlow】梯度下降

写在前面 一直不太理解梯度下降算法是什么意思&#xff0c;今天我们就解开它神秘的面纱 写在中间 线性回归方程 如果要求出一条直线&#xff0c;我们只需知道直线上的两个不重合的点&#xff0c;就可以通过解方程组来求出直线 但是&#xff0c;如果我们选取的这两个点不在直…

GD32F103VE外部中断

GD32F103VE外部中断线线0~15&#xff0c;对应外部IO口的输入中断。它有7个中断向量&#xff0c;外部中断线0 ~ 4分别对应EXTI0_IRQn ~ EXTI4_IRQn中断向量&#xff1b;外部中断线 5 ~ 9 共用一个 EXTI9_5_IRQn中断向量&#xff1b;外部中断线10~15 共用一个 EXTI15_10_IRQn中断…

MySQL数据库:表的约束

表的约束&#xff0c;实质上就是用数据类型去约束字段&#xff0c;但是数据类型的约束手法很单一&#xff0c;比如&#xff0c;我们在设置身份证号这个字段&#xff0c;数据类型唯一起的约束是它属于char类型或者varchar类型&#xff0c;不能是浮点型也不能是日期时间类型&…

.net 6 efcore一个model映射到多张表(非使用IEntityTypeConfiguration)

现在有两张表&#xff0c;结构一模一样&#xff0c;我又不想创建两个一模一样的model&#xff0c;就想一个model映射到两张表 废话不多说直接上代码 安装依赖包 创建model namespace oneModelMultiTable.Model {public class Test{public int id { get; set; }public string…

Linux服务器大量日志如何快速定位

Linux服务器大量日志如何快速定位 在生产环境&#xff0c;定位问题&#xff0c;经常会遇到日志文件特别多的情况&#xff0c;经常会遇到日志比较难拿的情况&#xff0c;所以有什么方法可以快速拿日志&#xff1f;除了在代码里很好的打印关键日志信息外&#xff0c;也需要掌握L…

RabbitMQ 教程 | 第10章 网络分区

&#x1f468;&#x1f3fb;‍&#x1f4bb; 热爱摄影的程序员 &#x1f468;&#x1f3fb;‍&#x1f3a8; 喜欢编码的设计师 &#x1f9d5;&#x1f3fb; 擅长设计的剪辑师 &#x1f9d1;&#x1f3fb;‍&#x1f3eb; 一位高冷无情的编码爱好者 大家好&#xff0c;我是 DevO…