"""
Yolo V1 by tensorflow
""" import numpy as np
import tensorflow._api.v2.compat.v1 as tf
tf.disable_v2_behavior() import cv2 # leaky_relu激活函数
def leak_relu(x, alpha=0.1):
return tf.maximum(alpha * x, x) class Yolo(object):
def __init__(self, weights_file, verbose=True):
# 一个开关,打开时,打印清晰的训练数据
self.verbose = verbose
# 检测参数
# 将图片分割成7*7的格子(注意不是真的分割)
self.S = 7
# 每个格子预测2个box
self.B = 2
# yolo可以预测的物体(20类,如猫,狗,车等)
self.classes = ["aeroplane", "bicycle", "bird", "boat", "bottle",
"bus", "car", "cat", "chair", "cow", "diningtable",
"dog", "horse", "motorbike", "person", "pottedplant",
"sheep", "sofa", "train","tvmonitor"]
# 分类的个数 20
self.C = len(self.classes)
# 绘制坐标值 [7, 7 ,2]
# transpose的作用是调换数组的行列值,类似于求矩阵的转置
# # 改变数组的shape
self.x_offset = np.transpose(np.reshape(np.array([np.arange(self.S)]*self.S*self.B),
[self.B, self.S, self.S]), [1, 2, 0])
self.y_offset = np.transpose(self.x_offset, [1, 0, 2])
# 类别置信度分数阈值
self.threshold = 0.2
# IOU阈值,小于0.4的会过滤掉
self.iou_threshold = 0.4
# NMS选择的边界框的最大数量
self.max_output_size = 10
# 这其实不是安装错误,是因为在新的Tensorflow 2.0版本中已经移除了Session这一模块
# self.sess = tf.Session()
self.sess = tf.compat.v1.Session()
# 【1】搭建网络模型(预测):模型的主体网络部分,这个网络将输出[batch,7*7*30]的张量
self._build_net()
# 【2】解析网络的预测结果:先判断预测框类别,再NMS:非极大值抑制(Non-Maximum Suppression,NMS),顾名思义就是抑制不是极大值的元素,可以理解为局部最大搜索。
self._build_detector()
# 解析权重
self._load_weights(weights_file) # 【1】搭建网络模型(预测):模型的主体网络部分,这个网络将输出[batch,7*7*30]的张量
def _build_net(self):
# 打印状态信息
if self.verbose:
print("Start to build the network ...")
# 输入、输出用占位符,因为尺寸一般不会改变
# None表示不确定,为了自适应batchsize
# placeholder()函数是在神经网络构建graph的时候在模型中的占位,此时并没有把要输入的数据传入模型,它只会分配必要的内存。等建立session。
# 在会话中,运行模型的时候通过feed_dict()函数向占位符喂入数据。
self.images = tf.placeholder(tf.float32, [None, 448, 448, 3])
# 搭建网络模型
net = self._conv_layer(self.images, 1, 64, 7, 2)
net = self._maxpool_layer(net, 1, 2, 2)
net = self._conv_layer(net, 2, 192, 3, 1)
net = self._maxpool_layer(net, 2, 2, 2)
net = self._conv_layer(net, 3, 128, 1, 1)
net = self._conv_layer(net, 4, 256, 3, 1)
net = self._conv_layer(net, 5, 256, 1, 1)
net = self._conv_layer(net, 6, 512, 3, 1)
net = self._maxpool_layer(net, 6, 2, 2)
net = self._conv_layer(net, 7, 256, 1, 1)
net = self._conv_layer(net, 8, 512, 3, 1)
net = self._conv_layer(net, 9, 256, 1, 1)
net = self._conv_layer(net, 10, 512, 3, 1)
net = self._conv_layer(net, 11, 256, 1, 1)
net = self._conv_layer(net, 12, 512, 3, 1)
net = self._conv_layer(net, 13, 256, 1, 1)
net = self._conv_layer(net, 14, 512, 3, 1)
net = self._conv_layer(net, 15, 512, 1, 1)
net = self._conv_layer(net, 16, 1024, 3, 1)
net = self._maxpool_layer(net, 16, 2, 2)
net = self._conv_layer(net, 17, 512, 1, 1)
net = self._conv_layer(net, 18, 1024, 3, 1)
net = self._conv_layer(net, 19, 512, 1, 1)
net = self._conv_layer(net, 20, 1024, 3, 1)
net = self._conv_layer(net, 21, 1024, 3, 1)
net = self._conv_layer(net, 22, 1024, 3, 2)
net = self._conv_layer(net, 23, 1024, 3, 1)
net = self._conv_layer(net, 24, 1024, 3, 1)
net = self._flatten(net)
net = self._fc_layer(net, 25, 512, activation=leak_relu)
net = self._fc_layer(net, 26, 4096, activation=leak_relu)
net = self._fc_layer(net, 27, self.S*self.S*(self.C+5*self.B))
self.predicts = net # 【2】解析网络的预测结果:先判断预测框类别,再NMS:非极大值抑制(Non-Maximum Suppression,NMS),顾名思义就是抑制不是极大值的元素,可以理解为局部最大搜索。
def _build_detector(self):
"""解释净输出,并得到预测的方框"""
# 原始图像的宽度和高度
self.width = tf.placeholder(tf.float32, name="img_w")
self.height = tf.placeholder(tf.float32, name="img_h")
# 网络回归[batch,7*7*30]
idx1 = self.S * self.S * self.C
idx2 = idx1 + self.S * self.S * self.B
# 分类预测值 [batch_size, 7, 7, 20]
class_probs = tf.reshape(self.predicts[0, :idx1], [self.S, self.S, self.C])
# 置信度 [batch_size, 7, 7, 2]
confs = tf.reshape(self.predicts[0, idx1:idx2], [self.S, self.S, self.B])
# 预测边框 [batch_size, 7, 7, 2, 4]
boxes = tf.reshape(self.predicts[0, idx2:], [self.S, self.S, self.B, 4]) # 将x,y转换为相对于图像左上角的坐标
# w,h的预测是平方根乘以图像的宽度和高度 tf.constant:创建常量的函数
boxes = tf.stack([(boxes[:, :, :, 0] + tf.constant(self.x_offset, dtype=tf.float32)) / self.S * self.width,
(boxes[:, :, :, 1] + tf.constant(self.y_offset, dtype=tf.float32)) / self.S * self.height,
tf.square(boxes[:, :, :, 2]) * self.width,
tf.square(boxes[:, :, :, 3]) * self.height], axis=3) # 类别置信度分数:[S,S,B,1]*[S,S,1,C]=[S,S,B,类别置信度C]
# expand_dims 主要作用,就是增加一个维度。
scores = tf.expand_dims(confs, -1) * tf.expand_dims(class_probs, 2)
scores = tf.reshape(scores, [-1, self.C]) # [S*S*B, C]
boxes = tf.reshape(boxes, [-1, 4]) # [S*S*B, 4]
# 只选择类别置信度最大的值作为box的类别、分数
# tf.argmax(input,axis)根据axis取值的不同返回每行或者每列最大值的索引。
# axis=0 每一列的最大值的索引 索引从0开始
# axis=1 每一行的最大值的索引
box_classes = tf.argmax(scores, axis=1)
# print('box_classes',box_classes)
box_class_scores = tf.reduce_max(scores, axis=1) # 利用类别置信度阈值self.threshold,过滤掉类别置信度低的
filter_mask = box_class_scores >= self.threshold
# tf.boolean_mask 的作用是 通过布尔值 过滤元素
scores = tf.boolean_mask(box_class_scores, filter_mask)
boxes = tf.boolean_mask(boxes, filter_mask)
box_classes = tf.boolean_mask(box_classes, filter_mask)
print (scores,boxes,box_classes)
# NMS (不区分不同的类别)
# 中心坐标+宽高box (x, y, w, h) -> xmin=x-w/2 -> 左上+右下box (xmin, ymin, xmax, ymax),因为NMS函数是这种计算方式
# box (x, y, w, h) -> box (x1, y1, x2, y2)
_boxes = tf.stack([boxes[:, 0] - 0.5 * boxes[:, 2], boxes[:, 1] - 0.5 * boxes[:, 3],
boxes[:, 0] + 0.5 * boxes[:, 2], boxes[:, 1] + 0.5 * boxes[:, 3]], axis=1)
# 下面的这个车被多次检测到,存在多个边界框,保留概率最大的那个,去除掉与这个概率最大的边界框的IoU大于一个阙值的其余边界框。这个过程就成为非最大值抑制=NMS
# non_max_suppression 非极大抑制
nms_indices = tf.image.non_max_suppression(_boxes, scores,
self.max_output_size, self.iou_threshold)
# tf.gather 该接口的作用:就是抽取出params的第axis维度上在indices里面所有的index
self.scores = tf.gather(scores, nms_indices)
self.boxes = tf.gather(boxes, nms_indices)
self.box_classes = tf.gather(box_classes, nms_indices)
# # 卷积层:
# x输入;
# id:层数索引;
# num_filters:卷积核个数;
# filter_size:卷积核尺寸;
# stride:步长
def _conv_layer(self, x, id, num_filters, filter_size, stride):
# 卷积层
# as_list()[0] 行数 as_list()[1] 列数 ????????[-1]
#??????????????# 通道数
in_channels = x.get_shape().as_list()[-1]
# tf.get_variable跟tf.Variable都可以用来定义图变量
# 截断的产生正态分布的随机数,即随机数与均值的差值若大于两倍的标准差,则重新生成。
# shape,生成张量的维度
# mean,均值
# stddev,标准差
# # 均值为0标准差为0.1的正态分布,初始化权重w;shape=行*列*通道数*卷积核个数
weight = tf.Variable(tf.truncated_normal([filter_size, filter_size,in_channels, num_filters], stddev=0.1))
# 列向量
bias = tf.Variable(tf.zeros([num_filters,]))
# padding, 注意: 不用padding="SAME",否则可能会导致坐标计算错误
# 除法运算,保留商的整数部分
pad_size = filter_size // 2
# 上 下 左 右
pad_mat = np.array([[0, 0], [pad_size, pad_size], [pad_size, pad_size], [0, 0]])
#tf.pad:填充函数
x_pad = tf.pad(x, pad_mat)
#tf.nn.conv2d (input, filter, strides, padding, use_cudnn_on_gpu=None, data_format=None, name=None)
# 参数:
# input : 输入的要做卷积的图片,要求为一个张量,shape为 [ batch, in_height, in_width, in_channel ],其中batch为图片的数量,in_height 为图片高度,in_width 为图片宽度,in_channel 为图片的通道数,灰度图该值为1,彩色图为3。(也可以用其它值,但是具体含义不是很理解)
# filter: 卷积核,要求也是一个张量,shape为 [ filter_height, filter_width, in_channel, out_channels ],其中 filter_height 为卷积核高度,filter_width 为卷积核宽度,in_channel 是图像通道数 ,和 input 的 in_channel 要保持一致,out_channel 是卷积核数量。
# strides: 卷积时在图像每一维的步长,这是一个一维的向量,[ 1, strides, strides, 1],第一位和最后一位固定必须是1
# padding: string类型,值为“SAME” 和 “VALID”,表示的是卷积的形式,是否考虑边界。"SAME"是考虑边界,不足的时候用0去填充周围,"VALID"则不考虑
# use_cudnn_on_gpu: bool类型,是否使用cudnn加速,默认为true
# 在TensorFlow中使用tf.nn.conv2d实现“卷积”操作
conv = tf.nn.conv2d(x_pad, weight, strides=[1, stride, stride, 1], padding="VALID")
# tf.nn.bias_add:一个叫bias的向量加到一个叫value的矩阵上,是向量与矩阵的每一行进行相加,得到的结果和value矩阵大小相同。
output = leak_relu(tf.nn.bias_add(conv, bias))
if self.verbose:
print(" Layer %d: type=Conv, num_filter=%d, filter_size=%d, stride=%d, output_shape=%s" \
% (id, num_filters, filter_size, stride, str(output.get_shape())))
return output # x:输入;
# id:层数索引;
# num_out:输出尺寸;
# activation:激活函数
def _fc_layer(self, x, id, num_out, activation=None):
# 全连接层
# 通道数/维度
num_in = x.get_shape().as_list()[-1]
# 均值为0标准差为0.1的正态分布,初始化权重w;shape=行*列*通道数*卷积核个数
weight = tf.Variable(tf.truncated_normal([num_in, num_out], stddev=0.1))
# 列向量
bias = tf.Variable(tf.zeros([num_out,]))
# tf.nn.xw_plus_b:主要的功能就是算X*W+b这个函数
# x:输入的矩阵,一般是网络结构上一层的输出,维度为[batch_size, in_units]表示输入的样本数*每个样本用多少个单元表示
# weights: x的权重矩阵,一般都是可训练的。维度为[in_units, out_units]。注意的是第一个维度要和x的最后一个维度一样,因为需要和x进行矩阵相乘的计算
# biases:偏置。维度为一维,[out_units],注意和weights的最后一维一致,因为最终要加在XW的结果矩阵上。而X*W矩阵的维度为[batch_size, out_units]
output = tf.nn.xw_plus_b(x, weight, bias)
if activation:
output = activation(output)
if self.verbose:
print(" Layer %d: type=Fc, num_out=%d, output_shape=%s" \
% (id, num_out, str(output.get_shape())))
return output # 池化层
# x:输入;
# id:层数索引;
# pool_size:池化尺寸;
# stride:步长
def _maxpool_layer(self, x, id, pool_size, stride):
# max pooling是CNN当中的最大值池化操作
# 池化后的尺寸一定会变小吗????
#tf.nn.max_pool(value, ksize, strides, padding, name=None)
# 参数是四个,和卷积很类似:
# 第一个参数value:需要池化的输入,一般池化层接在卷积层后面,所以输入通常是feature map,依然是[batch, height, width, channels]这样的shape
# 第二个参数ksize:池化窗口的大小,取一个四维向量,一般是[1, height, width, 1],因为我们不想在batch和channels上做池化,所以这两个维度设为了1
# 第三个参数strides:和卷积类似,窗口在每一个维度上滑动的步长,一般也是[1, stride,stride, 1]
# 第四个参数padding:和卷积类似,可以取'VALID' 或者'SAME'
# 返回一个Tensor,类型不变,shape仍然是[batch, height, width, channels]这种形式
output = tf.nn.max_pool(x, [1, pool_size, pool_size, 1],
strides=[1, stride, stride, 1], padding="SAME")
if self.verbose:
print(" Layer %d: type=MaxPool, pool_size=%d, stride=%d, output_shape=%s" \
% (id, pool_size, stride, str(output.get_shape())))
return output # 扁平层:因为接下来会连接全连接层,例如[n_samples, 7, 7, 32] -> [n_samples, 7*7*32]
def _flatten(self, x):
# [batch,行,列,通道数channels] -> [batch,通道数channels,列,行]
tran_x = tf.transpose(x, [0, 3, 1, 2])
# 计算的是总共的神经元数量,第一个表示batch数量所以去掉
nums = np.product(x.get_shape().as_list()[1:])
# [batch,通道数channels,列,行] -> [batch,通道数channels*列*行],-1代表自适应batch数量
return tf.reshape(tran_x, [-1, nums]) # 【3】导入权重文件
def _load_weights(self, weights_file):
# 打印状态信息
if self.verbose:
print("Start to load weights from file:%s" %(weights_file))
# 初始化
#tf.train.Saver() 保存和加载模型
saver = tf.train.Saver()
# saver.restore导入/saver.save保存
saver.restore(self.sess, weights_file) def detect_from_file(self, image_file, imshow=True, deteted_boxes_file="boxes.txt",detected_image_file="detected_image.jpg"):
# 给定图像文件进行检测
# 读取图片
image = cv2.imread(image_file)
# 获取图片的高度和宽度
img_h, img_w, _ = image.shape
# 得到这张图片的 所有预测概率 预测的位置 种类
scores, boxes, box_classes = self._detect_from_image(image)
# 预测边框
predict_boxes = []
for i in range(len(scores)):
#self.classes[box_classes[i]]:预测的种类
#boxes[i, 0] x的位置
#boxes[i, 1] y的位置
#boxes[i, 2] w的位置 宽度 ???? 为啥是两倍
#boxes[i, 3] h的位置 高度 ???? 为啥是两倍
#scores[i] 准确率
predict_boxes.append((self.classes[box_classes[i]], boxes[i, 0],
boxes[i, 1], boxes[i, 2], boxes[i, 3], scores[i]))
# # deteted_boxes_file="boxes.txt"是最后坐标txt;detected_image_file="detected_image.jpg"是检测结果可视化图片
self.show_results(image, predict_boxes, imshow, deteted_boxes_file, detected_image_file) def _detect_from_image(self, image):
# 给定cv图像是否进行检测
# 为什么要加一个_
img_h, img_w,_ = image.shape
print(img_h,':',img_w)
# resize 重新改变图片的大小
img_resized = cv2.resize(image, (448, 448))
# 灰度化图片
img_RGB = cv2.cvtColor(img_resized, cv2.COLOR_BGR2RGB)
# 当参数为ndarray时,np.array会复制一个ndarray对象,np.asarray不会复制,而是和原来的占用同一个内存
img_resized_np = np.asarray(img_RGB)
# 填充 0 4D张量 将原始输入图片进行resize到(448,448,3),通过上述的扩展转换,使得输入的尺寸符合网络输入格式 [batch_size, width, high, channels]
_images = np.zeros((1, 448, 448, 3), dtype=np.float32)
# 使图像像素值介于-1~1之间 归一化处理
_images[0] = (img_resized_np / 255.0) * 2.0 - 1.0
#类别概率 边框位置 分类
scores, boxes, box_classes = self.sess.run([self.scores, self.boxes, self.box_classes],
# 输入参数 #输入图片 #图片尺寸
# feed_dict()函数向站位符喂数据
feed_dict={self.images: _images, self.width: img_w, self.height: img_h}) print(scores, boxes)
return scores, boxes, box_classes def show_results(self, image, results, imshow=True, deteted_boxes_file=None,detected_image_file=None):
# 显示检测框
# copy可能是复制image的一个副本吧
img_cp = image.copy()
if deteted_boxes_file:
f = open(deteted_boxes_file, "w")
# 画出 检测框
for i in range(len(results)):
# 根据x y w h 的值
x = int(results[i][1])
y = int(results[i][2])
w = int(results[i][3]) // 2
h = int(results[i][4]) // 2
if self.verbose:
print(" class: %s, [x, y, w, h]=[%d, %d, %d, %d], confidence=%f" % (results[i][0],
x, y, w, h, results[i][-1]))
# cv2.rectangle()方法用于在任何图像上绘制矩形。
# img_cp :它是要在其上绘制矩形的图像。
#(x - w, y - h):它是矩形的起始坐标。坐标表示为两个值的元组,即(X坐标值,Y坐标值)。
#(x + w, y + h):它是矩形的结束坐标。坐标表示为两个值的元组,即(X坐标值ÿ坐标值)。
# (0, 255, 0):它是要绘制的矩形的边界线的颜色。对于BGR,我们通过一个元组。例如:(255,0,0)为蓝色。
# -2:它是矩形边框线的粗细像素。厚度-1像素将以指定的颜色填充矩形形状。
cv2.rectangle(img_cp, (x - w, y - h), (x + w, y + h), (0, 255, 0), 2)
# 有可能是上面的灰色框,用来存放类别 和置信度
cv2.rectangle(img_cp, (x - w, y - h - 20), (x + w, y - h), (125, 125, 125), -1)
# cv2.putText(img, str(i), (123,456)), font, 2, (0,0,0), 1)
# 各参数依次是:图片,类别,左上角坐标,字体,字体大小,颜色,字体粗细
cv2.putText(img_cp, results[i][0] + ' : %.2f' % results[i][5], (x - w + 5, y - h - 7),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 1)
# 保存obj检测结果为txt文件
if deteted_boxes_file:
f.write(results[i][0] + ',' + str(x) + ',' + str(y) + ',' +
str(w) + ',' + str(h)+',' + str(results[i][5]) + '\n')
# 暂时不知道是啥意思。。。。。。。。。。。
if imshow:
cv2.imshow('YOLO_small detection', img_cp)
cv2.waitKey(1)
if detected_image_file:
cv2.imwrite(detected_image_file, img_cp)
if deteted_boxes_file:
f.close()
# 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
if __name__ == "__main__":
# YOLO_small.ckpt这个需要自己下载
yolo_net = Yolo(r'D:\pycharm\yolo\YOLO_small.ckpt')
yolo_net.detect_from_file(r"6.jpg")

非极大值抑制算法(non maximum suppression, NMS)
NMS算法主要解决的是一个目标被多次检测的问题
首先从所有的检测框中找到置信度最大的那个框,然后挨个计算其与剩余框的IOU,如果其值大于一定阈值(重合度过高),那么就将该框剔除;然后对剩余的检测框重复上述过程,直到处理完所有的检测框。Yolo预测过程也需要用到NMS算法。

首先是优点,Yolo采用一个CNN网络来实现检测,是单管道策略,其训练与预测都是end-to-end,所以Yolo算法比较简洁且速度快。该算法最大的不足,就是对一些临近的小物体的识别效果不是太好,例如成群结队的小鸟。通常做回归问题的时候都会将输出进行归一化,否则可能导致各个输出维度的取值范围差别很大,进而导致训练的时候,网络更关注数值大的维度。因为数值大的维度,算loss相应会比较大,为了让这个loss减小,那么网络就会尽量学习让这个维度loss变小,最终导致区别对待。

深度学习 YOLO v1 源码+笔记的更多相关文章

  1. 【神经网络与深度学习】Caffe源码中各种依赖库的作用及简单使用

    1.      Boost库:它是一个可移植.跨平台,提供源代码的C++库,作为标准库的后备. 在Caffe中用到的Boost头文件包括: (1).shared_ptr.hpp:智能指针,使用它可以不 ...

  2. Golang构建HTTP服务(一)--- net/http库源码笔记

    搭建一个简单的Go Web服务器 Go语言标准库 - net/http 在学习Go语言有一个很好的起点,Go语言官方文档很详细,今天我们学习的Go Web服务器的搭建就需要用到Go语言官方提供的标准库 ...

  3. 全方位深度剖析PHP7底层源码(已完结)

    第1章 课程介绍本章主要介绍课程要讲的知识点,以及课程要求等. 第2章 PHP7的新特性本章主要介绍PHP7的新特性,做基准测试,与PHP5对比验证PHP7的性能提升程度,引出对PHP7源码学习的必要 ...

  4. 线程池 ThreadPoolExecutor 原理及源码笔记

    前言 前面在学习 JUC 源码时,很多代码举例中都使用了线程池 ThreadPoolExecutor,并且在工作中也经常用到线程池,所以现在就一步一步看看,线程池的源码,了解其背后的核心原理. 公众号 ...

  5. 厉害!这份阿里面试官 甩出的Spring源码笔记,GitHub上已经爆火

    前言 时至今日,Spring 在 Java 生态系统与就业市场上,面试出镜率之高,投产规模之广,无出其右.随着技术的发展,Spring 从往日的 IoC 框架,已发展成 Cloud Native 基础 ...

  6. [阿里DIEN] 深度兴趣进化网络源码分析 之 Keras版本

    [阿里DIEN] 深度兴趣进化网络源码分析 之 Keras版本 目录 [阿里DIEN] 深度兴趣进化网络源码分析 之 Keras版本 0x00 摘要 0x01 背景 1.1 代码进化 1.2 Deep ...

  7. Zepto源码笔记(一)

    最近在研究Zepto的源码,这是第一篇分析,欢迎大家继续关注,第一次写源码笔记,希望大家多指点指点,第一篇文章由于首次分析原因不会有太多干货,希望后面的文章能成为各位大大心目中的干货. Zepto是一 ...

  8. redis源码笔记(一) —— 从redis的启动到command的分发

    本作品采用知识共享署名 4.0 国际许可协议进行许可.转载联系作者并保留声明头部与原文链接https://luzeshu.com/blog/redis1 本博客同步在http://www.cnblog ...

  9. AsyncTask源码笔记

    AsyncTask源码笔记 AsyncTask在注释中建议只用来做短时间的异步操作,也就是只有几秒的操作:如果是长时间的操作,建议还是使用java.util.concurrent包中的工具类,例如Ex ...

  10. Java Arrays 源码 笔记

    Arrays.java是Java中用来操作数组的类.使用这个工具类可以减少平常很多的工作量.了解其实现,可以避免一些错误的用法. 它提供的操作包括: 排序 sort 查找 binarySearch() ...

随机推荐

  1. vCenter报错:Vmware vAPI Endpoint

    vCenter报错:Vmware vAPI Endpoint 问题现象: 平台警报1:设备管理运行状况警报 平台警报2:Vmware vAPI Endpoint服务运行状况警报 vcenter版本:v ...

  2. kali问题排查

    kali从2020的更新到最新版就卡在了启动界面 猜想会不会是内核的问题,选择到这个最新内核就可以正常进入系统了 由于觉得这样启动太过于麻烦,想办法把这个内核作为默认启动内核,从网上了解到要修改/et ...

  3. 记一次Native memory leak排查过程

    1 问题现象 路由计算服务是路由系统的核心服务,负责运单路由计划的计算以及实操与计划的匹配.在运维过程中,发现在长期不重启的情况下,有TP99缓慢爬坡的现象.此外,在每周例行调度的试算过程中,能明显看 ...

  4. SpringBoot 如何优雅的进行全局异常处理?

    在SpringBoot的开发中,为了提高程序运行的鲁棒性,我们经常需要对各种程序异常进行处理,但是如果在每个出异常的地方进行单独处理的话,这会引入大量业务不相关的异常处理代码,增加了程序的耦合,同时未 ...

  5. SSE图像算法优化系列三十一:RGB2HSL/RGB2HSV及HSL2RGB/HSV2RGB的指令集优化-上。

    RGB和HSL/HSV颜色空间的相互转换在我们的图像处理中是有着非常广泛的应用的,无论是是图像调节,还是做一些肤色算法,HSL/HSV颜色空间都非常有用,他提供了RGB颜色空间不具有的一些独特的特性, ...

  6. 即构✖叮咚课堂:行业第一套AI课堂解决方案是怎么被实现的?

    AI走进教育,是传统教育的一次迭代进化 在教育问题上,我们看到两类话题最容易引发公众讨论:教育公平和个性化教育,"互联网+教育"有可能解决第一类话题,"AI教育" ...

  7. git: failed to push some refs to

    错误原因 没有添加readme文件 解决方案 git pull --rebase origin master 至此问题解决

  8. 使用 VirtualBox+Vagrant 创建 CentOS7 虚拟机

    一.准备工作 1.1 软件下载 VirtualBox:Downloads – Oracle VM VirtualBox Vagrant:Install | Vagrant | HashiCorp De ...

  9. Prompt Playground 7月开发记录

    Prompt Playground 2023年7月开发记录 上个月的时候,出于日常工作需求,做了一个简单的提示词调试工具 Prompt Playground. 这个工具的初衷是为了方便测试,所以没有做 ...

  10. avue-crud属性配置项参数笔记分享

    Avue 是一个基于Element-plus低代码前端框架,它使用JSON 配置来生成页面,可以减少页面开发工作量,极大提升效率: 虽然Avue官网上面都有这些配置说明,但是如果刚开始接触不熟悉框架的 ...