• 入职算法工程师后敲的非常有趣使用的小工具


    NOTE:代码仅用来参考,没时间解释啦!

    🍉一、自动从数据库从抽取数据。

    在某台服务器中,从存放数据集的数据库自动抽取标注好的数据标签,这一步操作有什么用呢?当我们发现我们数据不均衡的时候,就如上图右边部分。我们可以从数据库中抽取缺少的数据标签进行填充。

    1. import os
    2. import shutil
    3. # from get_structs import print_file_structure
    4. import random
    5. def print_file_structure(file_path, indent=''):
    6. if os.path.isfile(file_path):
    7. print(indent + '├── ' + os.path.basename(file_path))
    8. elif os.path.isdir(file_path):
    9. print(indent + '├── ' + os.path.basename(file_path))
    10. for root, dirs, files in os.walk(file_path):
    11. for name in dirs:
    12. print(indent + '│ ├── ' + name)
    13. for name in files:
    14. print(indent + '│ └── ' + name)
    15. break # Only print files in the top-level directory
    16. break # Only print directories in the top-level directory
    17. else:
    18. print('无效的文件路径')
    19. def from_dataset_get_data_label(source_dataset_path, label):
    20. subFiles = os.listdir(source_dataset_path)
    21. if label not in subFiles:
    22. print("您输入的标签名无效,不存在于test子目录中!")
    23. return
    24. target_path = os.path.join(source_dataset_path, label)
    25. label_lenght = count_jpg_files(target_path)
    26. print("<{}>标签的数量统计为:【{}】".format(label, label_lenght))
    27. print('------------------------------------')
    28. all_need_img_paths = []
    29. all_need_xml_paths = []
    30. for file_name in os.listdir(target_path):
    31. subPath = os.path.join(target_path, file_name)
    32. if not os.path.isdir(subPath):
    33. continue
    34. for data_name in os.listdir(subPath):
    35. if data_name.endswith('.jpg'):
    36. xml_file = os.path.splitext(data_name)[0] + '.xml'
    37. if os.path.exists(os.path.join(subPath, xml_file)):
    38. all_need_img_paths.append(os.path.join(subPath, data_name))
    39. all_need_xml_paths.append(os.path.join(subPath, xml_file))
    40. # print(all_need_img_paths[:5])
    41. print("统计有xml的图片数量:",len(all_need_img_paths))
    42. print('------------------------------------')
    43. get_num = int(input("请输入您要随机抽取的数据数量:"))
    44. print('------------------------------------')
    45. if get_num > len(all_need_img_paths):
    46. get_num = len(all_need_img_paths) - 1
    47. random_indexs = random.sample(range(len(all_need_img_paths)), get_num)
    48. print("请注意!所有文件都会复制到工作目录,请慎重选择工作目录。")
    49. print('------------------------------------')
    50. opt = input("请选择您的移动方式:[cp/mv]")
    51. print('------------------------------------')
    52. while opt not in ['cp', 'mv']:
    53. opt = input("[ERROR]请选择您的移动方式:[cp/mv]")
    54. print('------------------------------------')
    55. if opt == 'cp':
    56. for inx in random_indexs:
    57. wd = os.getcwd()
    58. if not os.path.exists(wd + '/' + 'images'):
    59. os.makedirs(wd + '/' + 'images')
    60. if not os.path.exists(wd + '/' + 'Annotations'):
    61. os.makedirs(wd + '/' + 'Annotations')
    62. img_path = all_need_img_paths[inx]
    63. shutil.copyfile(img_path, wd + '/' + 'images/' + img_path.split('/')[-1])
    64. xml_path = all_need_xml_paths[inx]
    65. shutil.copyfile(xml_path, wd + '/' + 'Annotations/' + xml_path.split('/')[-1])
    66. elif opt == 'mv':
    67. pass
    68. print("在上列操作中您选择了{}标签,从中抽取了{}数据量,并且使用{}方式放到了{}工作目录下。".format(label, get_num, opt, wd))
    69. print('------------------------------------')
    70. def count_jpg_files(path):
    71. count = 0
    72. for root, dirs, files in os.walk(path):
    73. for file in files:
    74. if file.endswith('.jpg'):
    75. xml_file = os.path.splitext(file)[0] + '.xml'
    76. if os.path.exists(os.path.join(root, xml_file)):
    77. count += 1
    78. return count
    79. if __name__ == "__main__":
    80. source_dataset_path = '/data/personal/chz/find_allimgs_label/test'
    81. use_labels = ["zsd_m","zsd_l","fhz_h","fhz_f","kk_f","kk_h","fhz_bs", "fhz_ycn","fhz_wcn","fhz_red_h", "fhz_green_f", "fhz_m", "bs_ur", "bs_ul", "bs_up", "bs_down", "fhz_ztyc", "bs_right", "bs_left", "bs_dl", "bs_dr", "kgg_ybh", "kgg_ybf", "yljdq_flow", "yljdq_stop"]
    82. print_file_structure(source_dataset_path, "")
    83. print('------------------------------------')
    84. label = input("请您根据上列中的test菜单,选取您想要的标签:")
    85. print('------------------------------------')
    86. from_dataset_get_data_label(source_dataset_path, label)

    🍉二、 自动从指定minIo拉取图片到另外一台minIO

    1. import minio
    2. import pymysql
    3. import openpyxl
    4. import os
    5. def get_data_from_mysql():
    6. # 连接数据库-
    7. conn = pymysql.connect(host="10.168.1.94", user="", passwd="", db="RemotePatrolDB", port=, charset="utf8")
    8. cur = conn.cursor() # 创建游标对象
    9. # 查询表中数据
    10. cur.execute("SELECT * FROM CorrectPoint;")
    11. df = cur.fetchall() # 获取所有数据
    12. imageUrls = []
    13. for data in df:
    14. imageUrls.append(data[15])
    15. # print(data[15])
    16. cur.close()
    17. conn.close()
    18. return imageUrls
    19. def save_for_excel(df):
    20. wb = openpyxl.Workbook()
    21. ws = wb.active
    22. for row in df:
    23. ws.append(row)
    24. wb.save("文件名.xlsx")
    25. # 从minio上面拉取图片
    26. def load_data_minio(bucket: str, imageUrls):
    27. minio_conf = {
    28. 'endpoint': '10.168.1.96:9000',
    29. 'access_key': '',
    30. 'secret_key': '',
    31. 'secure': False
    32. }
    33. client = minio.Minio(**minio_conf)
    34. if not client.bucket_exists(bucket):
    35. return None
    36. root_path = os.path.join("imageUrlFromminIO")
    37. for imageUrl in imageUrls:
    38. imageUrl = imageUrl.split('/')[-1]
    39. data = client.get_object(bucket, imageUrl)
    40. save_path = os.path.join(root_path, imageUrl)
    41. with open(save_path, 'wb') as file_data:
    42. for d in data.stream(32 * 1024):
    43. file_data.write(d)
    44. return data.data
    45. # 上传图片到minio
    46. def up_data_minio(bucket: str, image_Urls_path='imageUrlFromminIO'):
    47. # TODO:minio_conf唯一要修改的地方!
    48. minio_conf = {
    49. 'endpoint': '192.168.120.188',
    50. 'access_key': '',
    51. 'secret_key': '',
    52. 'secure': False
    53. }
    54. for im_name in os.listdir(image_Urls_path):
    55. client = minio.Minio(**minio_conf)
    56. '''
    57. client.fput_object('mybucket', 'myobject.jpg', '/path/to/myobject.jpg', content_type='image/jpeg')
    58. '''
    59. client.fput_object(bucket_name=bucket, object_name=im_name,
    60. file_path=os.path.join(image_Urls_path, im_name),
    61. content_type='image/jpeg'
    62. )
    63. def download():
    64. # NOTE:Step:1 拉取数据库信息
    65. imageUrls = get_data_from_mysql()
    66. # NOTE:Step:2 把图片从96的minio上面拉下来
    67. print(type(load_data_minio("test", imageUrls)))
    68. def upload():
    69. # NOTE:Step:3 把拉下来的图片传上去给XXX服务器的minio
    70. up_data_minio("test", image_Urls_path='imageUrlFromminIO')
    71. if __name__ == "__main__":
    72. # 拉取使用
    73. download()
    74. # 上推使用
    75. # upload()
    76. '''
    77. 用于批量修改数据库ImagePath字段信息,替换为自己的ip。
    78. ---
    79. UPDATE CorrectPoint SET ImagePath=REPLACE(ImagePath, '10.168.1.96', '192.168.120.188');
    80. '''

    🍉三、目标检测画出中文框并且自动红底白字

    需要放一个文件到本地目录:

    链接:https://pan.baidu.com/s/1iEJKpqt-z_5yBJdenUABbA 
    提取码:uoox 
    --来自百度网盘超级会员V3的分享

    1. def cv2AddChineseText(self, img_ori, text, p1, box_color, textColor=(255, 255, 255), textSize=17):
    2. if (isinstance(img_ori, np.ndarray)): # 判断是否OpenCV图片类型
    3. img = Image.fromarray(cv2.cvtColor(img_ori, cv2.COLOR_BGR2RGB))
    4. # 创建一个可以在给定图像上绘图的对象
    5. draw = ImageDraw.Draw(img)
    6. # 字体的格式
    7. fontStyle = ImageFont.truetype(
    8. "simsun.ttc", textSize, encoding="utf-8")
    9. # 绘制文本
    10. text_width, text_height = draw.textsize(text, font=fontStyle)
    11. position = []
    12. outside_x = p1[0] + text_width + 3 < img.width
    13. outside_y = p1[1] - text_height - 3 >= 0
    14. position.append(p1[0] + 3 if outside_x else img.width - text_width)
    15. position.append(p1[1] - text_height - 3 if outside_y else p1[1] + 3)
    16. p2 = (position[0] + text_width, position[1] + text_height)
    17. image = cv2.rectangle(img_ori, position, p2, box_color, -1, cv2.LINE_AA) # filled
    18. img = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
    19. draw = ImageDraw.Draw(img)
    20. draw.text((position[0], position[1]), text, textColor, font=fontStyle)
    21. # 转换回OpenCV格式
    22. return cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR)
    23. def draw_boxs(self, boxes, image):
    24. for res in boxes:
    25. box = [res[0], res[1], res[2]+res[0], res[3]+res[1]]
    26. label = self.labels[res[4]]
    27. conf = round(res[5], 4)
    28. box = np.array(box[:4], dtype=np.int32) # xyxy
    29. line_width = int(3)
    30. txt_color = (255, 255, 255)
    31. box_color = (58, 56, 255)
    32. p1, p2 = (box[0], box[1]), (box[2], box[3])
    33. image = cv2.rectangle(image, p1, p2, box_color, line_width)
    34. tf = max(line_width - 1, 1) # font thickness
    35. box_label = '%s: %.2f' % (self.get_desc(label), conf)
    36. image = self.cv2AddChineseText(image, box_label, p1, box_color, txt_color)
    37. return image

    🍉四、标注得到的xml自动转成txt

    使用labelimage标注的文件是xml的,无法用来yolo训练,所以需要使用自动转换工具把xml都转换为txt。

    请确保目录结构如下:

    1. import os
    2. import xml.etree.ElementTree as ET
    3. import cv2
    4. import random
    5. from tqdm import tqdm
    6. from multiprocessing import Pool
    7. import numpy as np
    8. import shutil
    9. '''
    10. 优化之前:
    11. 1.把函数路径改为新的数据集,先运行一次,生成txt;
    12. 2.把新的数据集Images Annotations labels都手动放入 原生数据集;
    13. 3.再把路径改回来原生数据集,再运行一次,生成txt;
    14. 问题:
    15. (1)txt不是追加模式,虽然会在第三步被覆盖掉,但重复执行没必要。
    16. (2)有很多地方类似(1)其实是运行了两次的。
    17. 优化之后:
    18. 1.把函数路径改为新的数据集,运行一次,完成!
    19. '''
    20. random.seed(0)
    21. class Tools_xml2yolo(object):
    22. def __init__(self,
    23. img_path = r"ft_220/images",
    24. anno_path = r"ft_220/annotations_xml",
    25. label_path = r"ft_220/labels",
    26. themeFIle = 'ft_220',
    27. classes = [""],
    28. the_data_is_new=False
    29. ) -> None:
    30. self.img_path = img_path
    31. self.anno_path = anno_path
    32. self.label_path = label_path
    33. self.the_data_is_new = the_data_is_new
    34. self.classes = classes
    35. self.txt_path = themeFIle
    36. if the_data_is_new:
    37. self.ftest = open(os.path.join(self.txt_path,'test.txt'), 'a')
    38. self.ftrain = open(os.path.join(self.txt_path,'train.txt'), 'a')
    39. else:
    40. self.ftest = open(os.path.join(self.txt_path,'test.txt'), 'w')
    41. self.ftrain = open(os.path.join(self.txt_path,'train.txt'), 'w')
    42. train_percent = 1
    43. self.files = os.listdir(self.anno_path)
    44. num = len(self.files)
    45. # print('num image',num)
    46. list = range(num)
    47. tr = int(num * train_percent)
    48. self.train_list = random.sample(list, tr)
    49. print('len train', self.train_list)
    50. if not os.path.exists(self.label_path):
    51. os.makedirs(self.label_path)
    52. def resi(self, num):
    53. x = round(num, 6)
    54. x = str(abs(x))
    55. while len(x) < 8:
    56. x = x + str(0)
    57. return x
    58. def convert(self, size, box):
    59. dw = 1./size[0]
    60. dh = 1./size[1]
    61. x = (box[0] + box[1])/2.0 # x = x轴中点
    62. y = (box[2] + box[3])/2.0 # y = y轴中点
    63. w = box[1] - box[0] #w = width
    64. h = box[3] - box[2] # h = height
    65. x = self.resi(x*dw)
    66. w = self.resi(w*dw)
    67. y = self.resi(y*dh)
    68. h = self.resi(h*dh)
    69. return (x,y,w,h)
    70. # import glob
    71. def process(self, name):
    72. # found_flag = 0
    73. img_names = ['.jpg','.JPG','.PNG','.png','.jpeg']
    74. for j in img_names:
    75. img_name = os.path.splitext(name)[0] + j
    76. iter_image_path = os.path.join(self.img_path, img_name)
    77. # print("iter image path:", iter_image_path)
    78. if os.path.exists(iter_image_path):
    79. break
    80. xml_name = os.path.splitext(name)[0] + ".xml"
    81. txt_name = os.path.splitext(name)[0] + ".txt"
    82. string1 = ""
    83. # print(name)
    84. w,h = None, None
    85. iter_anno_path = os.path.join(self.anno_path, xml_name)
    86. iter_txt_path = os.path.join(self.label_path, txt_name)
    87. xml_file = ET.parse(iter_anno_path)
    88. root = xml_file.getroot()
    89. try:
    90. with open(iter_image_path, 'rb') as f:
    91. check = f.read()[-2:]
    92. if check != b'\xff\xd9':
    93. print('JPEG File collapse:', iter_image_path)
    94. a = cv2.imdecode(np.fromfile(iter_image_path,dtype=np.uint8),-1)
    95. cv2.imencode(".jpg", a)[1].tofile(iter_image_path)
    96. h,w = cv2.imdecode(np.fromfile(iter_image_path, dtype=np.uint8),-1).shape[:2]
    97. print('----------Rewrite & Read image successfully----------')
    98. else:
    99. h,w = cv2.imdecode(np.fromfile(iter_image_path,dtype=np.uint8),-1).shape[:2]
    100. except:
    101. print(iter_image_path)
    102. if (w is not None) and (h is not None):
    103. count = 0
    104. for child in root.findall('object'):
    105. if child != '':
    106. count = count + 1
    107. if count != 0:
    108. string1 = []
    109. for obj in root.iter('object'):
    110. cls = obj.find('name').text
    111. if cls in self.classes:
    112. cls_id = self.classes.index(cls)
    113. else:
    114. print(cls)
    115. continue
    116. xmlbox = obj.find('bndbox')
    117. b = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text),
    118. float(xmlbox.find('ymax').text))
    119. bb = self.convert((w, h), b)
    120. for a in bb:
    121. if float(a) > 1.0:
    122. print(iter_anno_path + "wrong xywh",bb)
    123. return
    124. string1.append(str(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n')
    125. out_file = open(iter_txt_path, "w")
    126. for string in string1:
    127. out_file.write(string)
    128. out_file.close()
    129. else:
    130. print('count=0')
    131. print(img_name)
    132. else:
    133. print('wh is none')
    134. def moveNewData(self, ):
    135. newImageDataPaths = os.listdir(self.img_path)
    136. newAnnotationPaths = os.listdir(self.anno_path)
    137. newLabelPaths = os.listdir(self.label_path)
    138. for idx in range(len(newAnnotationPaths)):
    139. shutil.move(os.path.join(self.img_path, newImageDataPaths[idx]), os.path.join(self.txt_path, "images",newImageDataPaths[idx]) )
    140. shutil.move(os.path.join(self.anno_path, newAnnotationPaths[idx]), os.path.join(self.txt_path, "Annotations",newAnnotationPaths[idx]) )
    141. shutil.move(os.path.join(self.label_path, newLabelPaths[idx]), os.path.join(self.txt_path, "labels",newLabelPaths[idx]) )
    142. def run(self,):
    143. pbar = tqdm(total=(len(self.files)))
    144. update = lambda *args: pbar.update()
    145. pool = Pool(6)
    146. for i, name in enumerate(self.files):
    147. self.process(name)
    148. print("Iter:[{}:{}]".format(i+1, len(self.files)))
    149. '''
    150. pool.apply_async必须在 if __main__ == "__main__"中被定义才可以使用;
    151. 这点以后优化得了,现在数据量少还用不上。
    152. 所以改成面对对象class类这样运行,多进程是不会有反应的。所以加了上面这个函数。
    153. 本来是没有的。
    154. '''
    155. pool.apply_async(self.process, args=(name), callback=update)
    156. # pbar.update(1)
    157. pool.close()
    158. pool.join()
    159. img_names = ['.jpg','.JPG','.PNG','.png', '.jpeg']
    160. for i, name in enumerate(self.files):
    161. for j in img_names:
    162. img_name = os.path.splitext(name)[0] + j
    163. iter_image_path = os.path.join(self.img_path, img_name)
    164. if os.path.exists(iter_image_path):
    165. break
    166. if i in self.train_list:
    167. self.ftrain.write(iter_image_path + "\n")
    168. else:
    169. self.ftest.write(iter_image_path + "\n")
    170. # writeAnnotation_path = os.path.join(self.img_path, os.path.splitext(name)[0] + '.xml')
    171. # print("写入:", iter_image_path, writeAnnotation_path )
    172. # 如果有只有图片没有xml的,需要生成空白txt
    173. if self.anno_path == '':
    174. imgs = os.listdir(self.img_path)
    175. for img_name in imgs:
    176. txt_name = os.path.basename(img_name).split('.')[0] + '.txt'
    177. if not os.path.exists(os.path.join(self.label_path, txt_name)):
    178. _ = open(os.path.join(self.label_path, txt_name),'w')
    179. self.ftrain.write(os.path.join(self.img_path, img_name) + "\n")
    180. if self.the_data_is_new:
    181. self.moveNewData()
    182. if __name__ == '__main__':
    183. # tool = Tools_xml2yolo()
    184. tool = Tools_xml2yolo(
    185. img_path='datasets/jzl_zhoushan_train/images/',
    186. anno_path='datasets/jzl_zhoushan_train/Annotations/',
    187. label_path='datasets/jzl_zhoushan_train/labels/',
    188. themeFIle='datasets/jzl_zhoushan_train/',
    189. classes=["zsd_m","zsd_l","fhz_h","fhz_f","kk_f","kk_h","fhz_bs", "fhz_ycn","fhz_wcn","fhz_red_h", "fhz_green_f", "fhz_m", "bs_ur", "bs_ul", "bs_up", "bs_down", "fhz_ztyc", "bs_right", "bs_left", "bs_dl", "bs_dr", "kgg_ybh", "kgg_ybf", "yljdq_flow", "yljdq_stop"],
    190. the_data_is_new=False)
    191. # themeFIle是原生数据集
    192. # 前面三个参数是新增数据集子集
    193. # the_data_is_new=True: 自动把images\Annotations\labels移到原生数据集对应images\Annotations\labels里面
    194. # 默认把xml转换为yolo训练所需的txt格式
    195. tool.run()

    🍉五、 使用yolo自动推理图片得到推理结果转换为训练所需xml

    1. import os
    2. import torch
    3. import xml.etree.ElementTree as ET
    4. from PIL import Image
    5. # 分类类别名称字典
    6. class_dict = {
    7. 'zsd_m': '指示灯灭',
    8. 'zsd_l': '指示灯亮',
    9. 'fhz_h': '分合闸-合',
    10. 'fhz_f': '分合闸-分',
    11. 'fhz_ztyc': '分合闸-状态异常',
    12. 'fhz_bs': '旋转把手',
    13. 'kk_f': '空气开关-分',
    14. 'kk_h': '空气开关-合',
    15. 'fhz_ycn': '分合闸-已储能',
    16. 'fhz_wcn': '分合闸未储能',
    17. 'fhz_red_h': '分合闸-红-合',
    18. 'fhz_green_f': '分合闸-绿-分',
    19. 'fhz_m': '分合闸-灭',
    20. 'bs_ur': '把手-右上',
    21. 'bs_ul': '把手-左上',
    22. 'bs_up': '把手-上',
    23. 'bs_down': '把手-下',
    24. 'bs_right': '把手-右',
    25. 'bs_left': '把手-左',
    26. 'bs_dl': '把手-左下',
    27. 'bs_dr': '把手-右下',
    28. "kgg_ybf": "开关柜-压板分",
    29. "kgg_ybh": "开关柜-压板合",
    30. "ddzsd_green":"带电指示灯-绿色",
    31. "ddzsd_red":"带电指示灯-红色"
    32. }
    33. def detect_and_save(model_path, folder_path, iter_start_index):
    34. # 加载模型
    35. model = torch.load(model_path, map_location=torch.device('cpu'))
    36. # 将模型设置为评估模式
    37. model.eval()
    38. # 遍历文件夹下的每一张图片
    39. for ind, file_name in enumerate(os.listdir(folder_path)):
    40. if ind <= iter_start_index:
    41. continue
    42. if file_name.endswith('.jpg') or file_name.endswith('.png'):
    43. # 打开图片
    44. img_path = os.path.join(folder_path, file_name)
    45. img = Image.open(img_path)
    46. # 进行推理
    47. results = model(img)
    48. # 生成xml文件
    49. root = ET.Element('annotation')
    50. folder = ET.SubElement(root, 'folder')
    51. folder.text = os.path.basename(folder_path)
    52. filename = ET.SubElement(root, 'filename')
    53. filename.text = file_name
    54. size = ET.SubElement(root, 'size')
    55. width = ET.SubElement(size, 'width')
    56. width.text = str(img.width)
    57. height = ET.SubElement(size, 'height')
    58. height.text = str(img.height)
    59. depth = ET.SubElement(size, 'depth')
    60. depth.text = str(3)
    61. for result in results.xyxy[0]:
    62. if result[-1] in class_dict:
    63. obj = ET.SubElement(root, 'object')
    64. name = ET.SubElement(obj, 'name')
    65. name.text = class_dict[result[-1]]
    66. bndbox = ET.SubElement(obj, 'bndbox')
    67. xmin = ET.SubElement(bndbox, 'xmin')
    68. xmin.text = str(int(result[0]))
    69. ymin = ET.SubElement(bndbox, 'ymin')
    70. ymin.text = str(int(result[1]))
    71. xmax = ET.SubElement(bndbox, 'xmax')
    72. xmax.text = str(int(result[2]))
    73. ymax = ET.SubElement(bndbox, 'ymax')
    74. ymax.text = str(int(result[3]))
    75. # 保存xml文件
    76. xml_path = os.path.join(folder_path, os.path.splitext(file_name)[0] + '.xml')
    77. tree = ET.ElementTree(root)
    78. tree.write(xml_path)
    79. if __name__ == "__main__":
    80. detect_and_save('./best.pt', './rmwrite_zhoushan/rmwrite_zhoushan', iter_start_index=180)
  • 相关阅读:
    Linux命令
    Bio-Helix丨Bio-Helix艾美捷Ponceaus S染色液说明书
    你的凭据不工作(Win10远程桌面Win10)详细解决路线
    详解TCP/IP协议第四篇:数据在网络中传输方式的分类概述
    Kafka在企业级应用中的实践
    基于SpringBoot+Vue的校园招聘管理系统(Java毕业设计)
    4.5 MongoDB 文档存储
    Go语言学习笔记——使用swagger生成api接口文档
    (C语言)求解二元一次方程组
    数据分析技能点-概括性度量
  • 原文地址:https://blog.csdn.net/qq_51831335/article/details/134429297