当前位置: 首页 > article >正文

基于深度学习的图片识别系统(上)

文章目录

  • 前言
  • 1.数据探索
  • 2.表格旋转
    • 2.1图像大小调节
    • 2.2二值化处理
    • 2.3线条提取
    • 2.4边缘检测
    • 2.5直线检测
    • 2.6倾斜角度统计
    • 2.7旋转
    • 2.8总结
  • 3.表格提取
    • 3.1基础工作
    • 3.2轮廓检测
    • 3.3裁剪
  • 4.线条去除
    • 4.1基础工作
    • 4.2线条提取
    • 4.3线条去除
  • 结语

前言

在实际工作中,通常有大量的表格需要在现场手工填写,然后再回录到信息系统,存在重复工作且复录时容易出现录错的问题,而采用中文手写体对相应表格内容进行实时识别和自动录入,人工进行检查校验即可,可减少大量人工工作量。本案例所实现的就是中文手写体的识别。该技术即图片识别技术,亦称光学字符识别(OCR)

1.数据探索

因为在实际开发环节是有很多数据的,这里为了方便起见,我们只选择了一张表格进行处理。在处理中使用了较多的opencv库因此如果没有的话,执行下述代码,可以安装一下

pip install opencv-python

首先我们先看一下图片的形状及外观:

import cv2


image = cv2.imread('data/test.jpg')
cv2.imshow("image",image)
cv2.waitKey(0)
cv2.destroyAllWindows()
image.shape

运行结果:
在这里插入图片描述
这里需要手动关闭这个窗口,不然会一直处于运行中。
在这里插入图片描述
通过结果发现,该图片是1820*2809*3形状的图片,3表示该图片是一个三通道的彩色图片,同时通过外观发现,该图片有些许倾斜,这就是属于数据预处理的一部分工作了。下面只对一张图片处理,多张处理流程保持一致。

下述为数据预处理环节,此处按步骤进行展开阐述。

2.表格旋转

因为通过上述运行结果发现,图片中的表格属于倾斜状态,因此需要对表格进行加工,调整。

2.1图像大小调节

因为此处只处理了一张图片,为了批处理操作的统一性,我们首先需要对图片大小进行调整。这里使用了imutils库,因此也需要安装。

pip install imutils

相关代码如下:

import imutils

image_height = 1080
img=imutils.resize(image, height=image_height)
img.shape

运行结果:
在这里插入图片描述
此时图片大小已经被调整为1080*1666*3,因为宽度默认等比例自适应,之所以宽度未作固定,防止数据丢失。

2.2二值化处理

首先需要将图片转换为灰度图,即单通道
代码如下:

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray,gray.shape

运行结果:
在这里插入图片描述
通过结果发现,图片已经转化为单通道,通过结果发现,有很多值是255,因为图片的大部分偏白色的原因。
其次阈值化处理,此处使用cv2.threshold函数

cv2.threshold 是 OpenCV 库中用于图像阈值处理的函数,它能够根据设定的阈值将图像的像素值划分为两个类别,常用于图像的二值化处理。

代码如下:

thresh,blur_gray=cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)
thresh

这里设置了cv2.THRESH_OTSU参数,因此你设置的阈值不会起作用,因为它会自动计算一个最优的阈值,结果如下
在这里插入图片描述
此时你可以再查看一下图片:

cv2.imshow("blur_gray",blur_gray)
cv2.waitKey(0)
cv2.destroyAllWindows()

运行结果:
在这里插入图片描述
结果发现,该图片已经二值反转,即高于阈值的置为0,低于阈值的置为255。

  • 注:使用此方法查看图片时,尽量不要通过小窗关闭该窗口,因为我的会出bug,显示检测不到窗口已关闭

2.3线条提取

因为目的为调整倾斜的表格,因此需要检测该表格的倾斜程度,即直线的倾斜程度,因此需要将直线提取出来。

cv2.getStructuringElement 是 OpenCV 里用于创建形态学操作所需结构元素的函数。形态学操作是基于形状的一系列图像处理操作,像膨胀、腐蚀、开运算和闭运算等,而结构元素则是形态学操作的基础,它定义了操作的形状和大小。

首先定义一个矩形元素,表示要提取的结构元素:
代码如下:

kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (30,1))
kernel.shape

在这里插入图片描述
通过结果发现,此时得到一个结构元素形状(1,30)
为什么呢?因为在 OpenCV 的 cv2.getStructuringElement 函数里,ksize 参数的顺序是 (width, height),也就是先宽度后高度,而不是正常的(height,width).
该元素提取的自然是一条横线,而不是竖线。
提取元素,并展示结果。
代码如下:

morhp_img = cv2.morphologyEx(blur_gray, cv2.MORPH_OPEN, kernel)
cv2.imshow("morhp_img",morhp_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

运行结果:
在这里插入图片描述
通过将结果发大就可以看出,该图像若干大小相等的小矩形组成,这就取决于定义的kernel

注:此操作不能通过小窗关闭该窗口,因为我的会出bug,显示检测不到窗口已关闭。所以此后就不采用此方式进行展示图片。
这里结果保存如下:

cv2.imwrite("data/linesDetected.jpg",morhp_img)

在这里插入图片描述
如果存储失败,会返回False,这里你可以查看是不是文件路径出现了错误
这里为了使线条更加明显可以再次膨胀,也可以在之前cv2.morphologyEx函数中修改iterations参数值

kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3),(-1,-1))
dilate_img = cv2.dilate(morhp_img, kernel,iterations=1)
cv2.imwrite("data/lines_dilated.jpg",dilate_img)

结果为:
在这里插入图片描述

2.4边缘检测

由于该直线过于粗,准确来说矩形的高度太大了,因此需要进行边缘检测,将矩形轮廓检测出来。这里使用Canny算法进行边缘检测,你也可以使用拉普拉斯滤波器

low_threshold = 0
high_threshold = 150
edges = cv2.Canny(dilate_img, low_threshold, high_threshold)
cv2.imwrite("data/lines_edges.jpg",edges)

运行结果:
在这里插入图片描述

2.5直线检测

这里使用霍夫曼直线检测算法,查找图中所有的直线段。
如果对该算法和Canny算法的具体算法流程感兴趣的话,我也可以写博客介绍以下,因为视觉刚学过,哈哈,不过不清楚也没关系,这里会用即可
代码如下:

import numpy as np

lines = cv2.HoughLinesP(edges, 1, np.pi/180, threshold=15,lines=np.array([]),minLineLength=50, maxLineGap=20)
lines,len(lines)

运行结果:
在这里插入图片描述
从结果来看,共计得到314条直线,每条直线有四个数值确定,因为二维空间中,每个点有两个数字组成,同时两点确定一条直线,因此每条直线由四个数字组成。

2.6倾斜角度统计

共计314条直线,需要统计它们的平均倾斜程度,然后再将图片旋转该角度,即可完成表格旋转处理。
代码如下:

angles = []
for line in lines:
    x1, y1, x2, y2 = line[0]
    angle = np.arctan2(y2 - y1, x2 - x1) * 180. / np.pi
    angles.append(angle)
median_angle = np.median(angles)
median_angle

运行结果:
在这里插入图片描述
结果表明,所有直线平均倾斜角度为 − 2.1 π 180 \frac{-2.1\pi}{180} 1802.1π°。

2.7旋转

此时已经得到平均倾斜角度,因此只需要将图片旋转调整即可。
代码如下:

from scipy import ndimage

img_rotated = ndimage.rotate(img, median_angle)
cv2.imwrite("data/rotated.jpg",img_rotated)

运行结果:
在这里插入图片描述
通过结果可以看到,表格已经完成旋转调整。

2.8总结

在实际开发环节,因为需要批量处理图片,因此一般将该操作封装成函数,代码如下:

def rotate_image(img_for_box_extraction_path):
     image_height = 1080
     image = cv2.imread(img_for_box_extraction_path)
     img = imutils.resize(image, height=image_height)
     gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
     (thresh, blur_gray) = cv2.threshold(gray, 128, 255, cv2.THRESH_BINARY_INV |
    cv2.THRESH_OTSU)
     kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (30, 1))
     morhp_img = cv2.morphologyEx(blur_gray, cv2.MORPH_OPEN, kernel, (-1, -1))
     cv2.imwrite('tmp/linesDetected.jpg', morhp_img)
     kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3), (-1, -1))
     lines_img = cv2.dilate(morhp_img, kernel, iterations=1)
     cv2.imwrite("tmp/lines_dilated.jpg", lines_img)
     low_threshold = 50
     high_threshold = 150
     edges = cv2.Canny(lines_img, low_threshold, high_threshold)
     lines = cv2.HoughLinesP(edges, rho=1, theta=np.pi / 180, threshold=15,
                             lines=np.array([]), minLineLength=50, maxLineGap=20)
     angles = []
     for line in lines:
         for x1, y1, x2, y2 in line:
             angle = math.degrees(math.atan2(y2 - y1, x2 - x1))
             angles.append(angle)
     median_angle = np.median(angles)
     img_rotated = ndimage.rotate(img, median_angle)
     print("Angle is {}".format(median_angle))
     cv2.imwrite('tmp/rotated.jpg', img_rotated)
     return img_rotated

3.表格提取

为了对表格中的内容进行识别,需要先将表格提取出来,然后再进行单元格的提取,这里采用OpenCV中的轮廓检测(findContours)方法提取表格的轮廓信息,并选择最大的四边形轮廓作为表格,将其裁剪出来。

3.1基础工作

这里的基础工作指的是大小调整,二值图像转换等操作,因为之前工作相对重复,这里就不再展开。

orig=img_rotated.copy()
image_height=500
ratio = orig.shape[0] / float(image_height)
image=imutils.resize(orig, height = image_height)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray=cv2.adaptiveThreshold(gray,255,cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY_INV,11,0)

对图片完成上述操作后,这里不再使用之前的可视化方法,而是用matplotlib进行可视化,代码如下:

from matplotlib import pyplot as plt

plt.subplot(1,2,1),plt.imshow(orig),plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(1,2,2),plt.imshow(gray),plt.title('gray')
plt.xticks([]), plt.yticks([])
plt.show()

运行结果:
在这里插入图片描述

3.2轮廓检测

cv2.findContours 是 OpenCV 库中一个非常重要的函数,主要用于在二值图像中查找轮廓。轮廓是由连续的点构成的曲线,代表了图像中物体的边界。

这里直接调用该函数:

countours,hierarchy=cv2.findContours(gray,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
len(countours)

这里参数是可选的,只要明白目的——将表格裁剪出来即可
运行结果:
在这里插入图片描述
从结果来看,共检测到了1728轮廓,这里我们可以进行可视化查看一下:

for i in range(len(countours)-1700):
    for point in countours[i].reshape(-1,2):
        cv2.circle(image,tuple(point),5,(0,0,255),4)
plt.imshow(image),plt.title('contours')
plt.xticks([]), plt.yticks([])
plt.show()

运行结果:
在这里插入图片描述
这里只可视化了28个轮廓点,而我们想找的是表格的四个角,显然这些并不符合条件,因此需要筛选出符合要求的轮廓点。
这里我们可以通过面积进行筛选,同时由于拍摄原因或纸张变形等原因产生形变,所以此处选用cv2.approxPolyDP进行辅助筛选。
代码如下:

countours=sorted(countours,key=cv2.contourArea,reverse=True)
screen_cut=None
for c in countours:
    epsilon=0.02*cv2.arcLength(c,True)
    approx=cv2.approxPolyDP(c,epsilon,True)
    area=cv2.contourArea(c)
    if area<2500:
        continue
    if len(approx)==4:
        screen_cut=approx
        break
if screen_cut is None:
    detected=0
    print("No contour detected")

运行结果:
在这里插入图片描述
因此已经轮廓,我们将它进行可视化,代码如下:

cv2.drawContours(image,[screen_cut],-1,(0,255,0),3)
for point in screen_cut.reshape(4,2):
    cv2.circle(image,tuple(point),5,(0,0,255),4)
plt.imshow(image),plt.title('contours')
plt.xticks([]), plt.yticks([])
plt.show()

运行结果:
在这里插入图片描述

3.3裁剪

上述已完成表格的获取,下面需要对图片进行裁剪,获取表格内容,这里裁剪已封装成函数,代码如下,具体功能就是,根据四个点,将图片进行裁剪。

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
      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))
      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)
      return cv2.warpPerspective(image, M, (maxWidth, maxHeight))

这里直接调用该函数并保存图像:

from shang import four_point_transform
warp=four_point_transform(orig,screen_cut.reshape(4,2)*ratio)
cv2.imwrite("data/warped.jpg",warp)

这里之所以使用导入语句,因为该函数并不在本文件中,如果是在一个文件中就不需要导入语句了。
运行结果如下:
在这里插入图片描述
在这里插入图片描述
结果表明,经过裁剪标记的四个点已经被裁剪掉了。下一步对表格中的线条进行去除。

4.线条去除

4.1基础工作

这里的基础工作和上面一样,裁剪、二值化处理。代码如下:

img_bin=imutils.resize(warp, height=1080)
img_bin=cv2.cvtColor(img_bin,cv2.COLOR_BGR2GRAY)
thresh,binary_src=cv2.threshold(img_bin, 128, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)
plt.imshow(binary_src),plt.title('binary')
plt.xticks([]), plt.yticks([])
plt.show()
cv2.imwrite("data/Image_bin_warp_invert.jpg",binary_src)

运行结果如下:
在这里插入图片描述

4.2线条提取

同理,这里我们使用cv2.getStructuringElement提取直线,通过腐蚀和膨胀将直线提取,不同的是,这里需要对横线和竖线都要进行操作,代码如下:

kernel_length_horizontal=binary_src.shape[1]//100
kernel_length_vertical=binary_src.shape[0]//30
kernel_vertical=cv2.getStructuringElement(cv2.MORPH_RECT,(1,kernel_length_vertical))
kernel_horizontal=cv2.getStructuringElement(cv2.MORPH_RECT,(kernel_length_horizontal,1))
kernel_vertical.shape,kernel_horizontal.shape

在这里插入图片描述
这里分别定义了横向和纵向处理的结构化元素,接着需要将线条提取出来,这里将其保存了。代码如下:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.3线条去除

将纵向表格线与横向表格线进行加操作,构建蒙版层,并和原始图像进行异或操作,即可去除线条,代码如下:

mask_img = verticle_lines_img + horizontal_lines_img
binary_src = np.bitwise_xor(binary_src, mask_img)
plt.imshow(binary_src),plt.title('binary')
plt.xticks([]), plt.yticks([])
plt.show()
cv2.imwrite("data/no_border_image.jpg", binary_src)

运行结果:
在这里插入图片描述
从结果来看,部分表格线并未处理干净,存在细小的线条,因此对其再次进行腐蚀和膨胀,代码如下:

clean_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 2))
img_erode = cv2.erode(binary_src, clean_kernel, iterations=1)
binary_src = cv2.dilate(img_erode, clean_kernel, iterations=1)
plt.imshow(binary_src),plt.title('binary')
plt.xticks([]), plt.yticks([])
plt.show()
cv2.imwrite("data/no_border_image_clean.jpg", binary_src)

运行结果:
在这里插入图片描述
完整版代码如下:

import cv2
import imutils
import numpy as np
import math
from math import *

from matplotlib import pyplot as plt
from scipy import ndimage
def rotate_image(img_for_box_extraction_path):
     image_height = 1080
     image = cv2.imread(img_for_box_extraction_path)
     img = imutils.resize(image, height=image_height)
     gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
     (thresh, blur_gray) = cv2.threshold(gray, 128, 255, cv2.THRESH_BINARY_INV |
    cv2.THRESH_OTSU)
     kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (30, 1))
     morhp_img = cv2.morphologyEx(blur_gray, cv2.MORPH_OPEN, kernel, (-1, -1))
     cv2.imwrite('tmp/linesDetected.jpg', morhp_img)
     kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3), (-1, -1))
     lines_img = cv2.dilate(morhp_img, kernel, iterations=1)
     cv2.imwrite("tmp/lines_dilated.jpg", lines_img)
     low_threshold = 50
     high_threshold = 150
     edges = cv2.Canny(lines_img, low_threshold, high_threshold)
     lines = cv2.HoughLinesP(edges, rho=1, theta=np.pi / 180, threshold=15,
                             lines=np.array([]), minLineLength=50, maxLineGap=20)
     angles = []
     for line in lines:
         for x1, y1, x2, y2 in line:
             angle = math.degrees(math.atan2(y2 - y1, x2 - x1))
             angles.append(angle)
     median_angle = np.median(angles)
     img_rotated = ndimage.rotate(img, median_angle)
     print("Angle is {}".format(median_angle))
     cv2.imwrite('tmp/rotated.jpg', img_rotated)
     return img_rotated
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
      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))
      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)
      return cv2.warpPerspective(image, M, (maxWidth, maxHeight))
def warp_image(image_height, image):
      orig = image.copy()
      ratio = image.shape[0] / float(image_height)
      image = imutils.resize(image, height=image_height)
      gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
      gray = cv2.adaptiveThreshold(gray, 255,cv2.ADAPTIVE_THRESH_MEAN_C,
     cv2.THRESH_BINARY_INV, 11, 0)
      # 可视化
      plt.subplot(121), plt.imshow(orig), plt.title('Original')
      plt.xticks([]), plt.yticks([])
      plt.subplot(122), plt.imshow(gray), plt.title('gray')
      plt.xticks([]), plt.yticks([])
      plt.show()
      # major = cv2.__version__.split('.')[0]
      # print(major)
      contours, hierarchy = cv2.findContours(gray, cv2.RETR_TREE,
     cv2.CHAIN_APPROX_SIMPLE)
      contours = sorted(contours, key=cv2.contourArea, reverse=True)
      screen_cnt = None
      for c in contours:
          epsilon = cv2.arcLength(c, True)
          approx = cv2.approxPolyDP(c, 0.02 * epsilon, True)
          area=cv2.contourArea(c)
          if area<2500:
              continue
          if len(approx) == 4:
              screen_cnt = approx
              break
      if screen_cnt is None:
          return -1,orig
      warped = four_point_transform(orig, screen_cnt.reshape(4, 2) * ratio)
      cv2.drawContours(image, [screen_cnt], -1, (0, 255, 0), 2)
      for point in screen_cnt.reshape(4, 2):
           cv2.circle(image, (point[0], point[1]), 5, (0, 0, 255), 4)
      plt.subplot(121), plt.imshow(image), plt.title('Original')
      plt.xticks([]), plt.yticks([])
      plt.subplot(122), plt.imshow(warped), plt.title('Warped')
      plt.xticks([]), plt.yticks([])
      plt.show()
      cv2.imwrite("tmp/warped.jpg", warped)
      return 0,warped
if __name__ == '__main__':
    # rotate_image("data/test.jpg")
    warp_image(500, rotate_image("data/test.jpg"))
    warped_image = cv2.imread("tmp/warped.jpg")
    img_bin = imutils.resize(warped_image, height=1080)
    img_bin = cv2.cvtColor(img_bin, cv2.COLOR_BGR2GRAY)
    (thresh, binary_src) = cv2.threshold(img_bin, 128, 255, cv2.THRESH_BINARY_INV |
                                         cv2.THRESH_OTSU)
    cv2.imwrite("tmp/Image_bin_warp_invert.jpg", binary_src)

    kernel_length_horizontal = np.array(binary_src).shape[1] // 100
    kernel_length_vertical = np.array(binary_src).shape[0] // 30

    verticle_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1, kernel_length_vertical))
    hori_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (kernel_length_horizontal, 1))
    img_temp1 = cv2.erode(binary_src, verticle_kernel, iterations=4)
    verticle_lines_img = cv2.dilate(img_temp1, verticle_kernel, iterations=4)
    cv2.imwrite("tmp/verticle_lines.jpg", verticle_lines_img)

    img_temp2 = cv2.erode(binary_src, hori_kernel, iterations=3)
    horizontal_lines_img = cv2.dilate(img_temp2, hori_kernel, iterations=3)
    cv2.imwrite("tmp/horizontal_lines.jpg", horizontal_lines_img)
    mask_img = verticle_lines_img + horizontal_lines_img
    binary_src = np.bitwise_xor(binary_src, mask_img)
    cv2.imwrite("tmp/no_border_image.jpg", binary_src)
    clean_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 2))
    img_erode = cv2.erode(binary_src, clean_kernel, iterations=1)
    binary_src = cv2.dilate(img_erode, clean_kernel, iterations=1)
    cv2.imwrite("tmp/no_border_image_clean.jpg", binary_src)

结语

至此数据预处理部分已算基本完成,虽然需要对表格进行裁剪,但此难度过大,因此本案例并不涉及该操作,后续训练会基于已处理好的数据集进行处理,撒花撒花!!!


http://www.kler.cn/a/597885.html

相关文章:

  • DFS深搜
  • AI开源项目
  • 优选算法的睿智之林:前缀和专题(一)
  • 2.2.盈亏平衡分析
  • MySQL 索引下推
  • React 18 如何定义变量,及赋值 与渲染
  • Linux常用的命令
  • [AI速读]混合验证方案:如何高效解决RISC-V向量扩展的验证难题
  • Nginx如何处理请求
  • Modbus协议编程读写流程图大全
  • DeepSeek对KVM环境下创建共享iSCSI存储的指导
  • 使用单调栈在O(n)时间复杂度内计算直方图中的最大矩形面积
  • 数据可视化在商业智能中的应用:从数据到洞察的桥梁
  • 信息安全基础
  • 2059-Authentication plugin ‘caching_sha2_password‘ cannot be loaded
  • 六十天前端强化训练之第二十八天之Composition 函数完全指南
  • 学习c++多线程前,回顾一下Linux的多线程
  • 圆弧插补相关算法汇总(C++和ST源代码)
  • CUDA 学习(4)——CUDA 编程模型
  • Android 系统进程启动Activity方法说明