常见的一些医疗图像处理步骤

2023-11-06

 

DICOM是医学图像中标准文件,这些文件包含了诸多的元数据信息(比如像素尺寸,每个维度的一像素代表真实世界里的长度)。此处以kaggle Data Science Bowl 数据集为例。

data-science-bowl-2017。数据列表如下:

dicom格式的图像

后缀为 .dcm

每个病人的一次扫描CT(scan)可能有几十到一百多个dcm数据文件(slices)。可以使用 python的dicom包读取,读取示例代码如下:

 
dicom.read_file('/data/lung_competition/stage1/7050f8141e92fa42fd9c471a8b2f50ce/498d16aa2222d76cae1da144ddc59a13.dcm')

其pixl_array包含了真实数据。

slices = [dicom.read_file(os.path.join(folder_name,filename)) for filename in os.listdir(folder_name)]
slices = np.stack([s.pixel_array for s in slices])

mhd格式是另外一种数据格式,来源于(LUNA2016)[https://luna16.grand-challenge.org/data/]。每个病人一个mhd文件和一个同名的raw文件。如下:

dicom格式的图像

一个mhd通常有几百兆,对应的raw文件只有1kb。mhd文件需要借助python的SimpleITK包来处理。SimpleITK 示例代码如下:

import SimpleITK as sitk
itk_img = sitk.ReadImage(img_file)
img_array = sitk.GetArrayFromImage(itk_img) # indexes are z,y,x (notice the ordering)
num_z, height, width = img_array.shape        #heightXwidth constitute the transverse plane
origin = np.array(itk_img.GetOrigin())      # x,y,z  Origin in world coordinates (mm)
spacing = np.array(itk_img.GetSpacing())    # spacing of voxels in world coor. (mm)

需要注意的是,SimpleITK的img_array的数组不是直接的像素值,而是相对于CT扫描中原点位置的差值,需要做进一步转换。转换步骤参考 SimpleITK图像转换

一个开源免费的查看软件 mango

dicom格式的图像

首先,需要明白的是医学扫描图像(scan)其实是三维图像,使用代码读取之后开源查看不同的切面的切片(slices),可以从不同轴切割

dicom格式的图像

如下图展示了一个病人CT扫描图中,其中部分切片slices

dicomæ ¼å¼çå¾å

 

其次,CT扫描图是包含了所有组织的,如果直接去看,看不到任何有用信息。需要做一些预处理,预处理中一个重要的概念是放射剂量,衡量单位为HU(Hounsfield Unit),下表是不同放射剂量对应的组织器官

substance HU
空气 -1000
-500
脂肪 -100到-50
0
CSF 15
30
血液 +30到+45
肌肉 +10到+40
灰质 +37到+45
白质 +20到+30
Liver +40到+60
软组织,constrast +100到+300
骨头 +700(软质骨)到+3000(皮质骨)
Hounsfield Unit = pixel_value * rescale_slope + rescale_intercept

一般情况rescale slope = 1, intercept = -1024。

上表中肺部组织的HU数值为-500,但通常是大于这个值,比如-320、-400。挑选出这些区域,然后做其他变换抽取出肺部像素点。

2.2 先载入必要的包

# -*- coding:utf-8 -*-
'''
this script is used for basic process of lung 2017 in Data Science Bowl
'''
import glob
import os
import pandas as pd
import SimpleITK as sitk
import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
import skimage, os
from skimage.morphology import ball, disk, dilation, binary_erosion, remove_small_objects, erosion, closing, reconstruction, binary_closing
from skimage.measure import label,regionprops, perimeter
from skimage.morphology import binary_dilation, binary_opening
from skimage.filters import roberts, sobel
from skimage import measure, feature
from skimage.segmentation import clear_border
from skimage import data
from scipy import ndimage as ndi
import matplotlib
#matplotlib.use('Agg')
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import dicom
import scipy.misc
import numpy as np

如下代码是载入一个扫描面,包含了多个切片(slices),我们仅简化的将其存储为python列表。数据集中每个目录都是一个扫描面集(一个病人)。有个元数据域丢失,即Z轴方向上的像素尺寸,也即切片的厚度 。所幸,我们可以用其他值推测出来,并加入到元数据中。

 
# Load the scans in given folder path
def load_scan(path):
    slices = [dicom.read_file(path + '/' + s) for s in os.listdir(path)]
    slices.sort(key = lambda x: int(x.ImagePositionPatient[2]))
    try:
        slice_thickness = np.abs(slices[0].ImagePositionPatient[2] - slices[1].ImagePositionPatient[2])
    except:
        slice_thickness = np.abs(slices[0].SliceLocation - slices[1].SliceLocation)
    for s in slices:
        s.SliceThickness = slice_thickness
    return slices

首先去除灰度值为 -2000的pixl_array,CT扫描边界之外的灰度值固定为-2000(dicom和mhd都是这个值)。第一步是设定这些值为0,当前对应为空气(值为0)

回到HU单元,乘以rescale比率并加上intercept(存储在扫描面的元数据中)。

 
def get_pixels_hu(slices):
    image = np.stack([s.pixel_array for s in slices])
    # Convert to int16 (from sometimes int16),
    # should be possible as values should always be low enough (<32k)
    image = image.astype(np.int16)
    # Set outside-of-scan pixels to 0
    # The intercept is usually -1024, so air is approximately 0
    image[image == -2000] = 0
    # Convert to Hounsfield units (HU)
    for slice_number in range(len(slices)):
        intercept = slices[slice_number].RescaleIntercept
        slope = slices[slice_number].RescaleSlope
        if slope != 1:
            image[slice_number] = slope * image[slice_number].astype(np.float64)
            image[slice_number] = image[slice_number].astype(np.int16)
        image[slice_number] += np.int16(intercept)
    return np.array(image, dtype=np.int16)

可以查看病人的扫描HU值分布情况

first_patient = load_scan(INPUT_FOLDER + patients[0])
first_patient_pixels = get_pixels_hu(first_patient)
plt.hist(first_patient_pixels.flatten(), bins=80, color='c')
plt.xlabel("Hounsfield Units (HU)")
plt.ylabel("Frequency")
plt.show()

dicom格式的图像

不同扫描面的像素尺寸、粗细粒度是不同的。这不利于我们进行CNN任务,我们可以使用同构采样。

一个扫描面的像素区间可能是[2.5,0.5,0.5],即切片之间的距离为2.5mm。可能另外一个扫描面的范围是[1.5,0.725,0.725]。这可能不利于自动分析。

常见的处理方法是从全数据集中以固定的同构分辨率重新采样,将所有的东西采样为1mmx1mmx1mm像素。

 
def resample(image, scan, new_spacing=[1,1,1]):
    # Determine current pixel spacing
    spacing = map(float, ([scan[0].SliceThickness] + scan[0].PixelSpacing))
    spacing = np.array(list(spacing))
    resize_factor = spacing / new_spacing
    new_real_shape = image.shape * resize_factor
    new_shape = np.round(new_real_shape)
    real_resize_factor = new_shape / image.shape
    new_spacing = spacing / real_resize_factor
    image = scipy.ndimage.interpolation.zoom(image, real_resize_factor, mode='nearest')
    return image, new_spacing

现在重新取样病人的像素,将其映射到一个同构分辨率 1mm x1mm x1mm。

 
pix_resampled, spacing = resample(first_patient_pixels, first_patient, [1,1,1])

使用matplotlib输出肺部扫描的3D图像方法。可能需要一两分钟。

 
def plot_3d(image, threshold=-300):
    # Position the scan upright,
    # so the head of the patient would be at the top facing the camera
    p = image.transpose(2,1,0)
    verts, faces = measure.marching_cubes(p, threshold)
    fig = plt.figure(figsize=(10, 10))
    ax = fig.add_subplot(111, projection='3d')
    # Fancy indexing: `verts[faces]` to generate a collection of triangles
    mesh = Poly3DCollection(verts[faces], alpha=0.1)
    face_color = [0.5, 0.5, 1]
    mesh.set_facecolor(face_color)
    ax.add_collection3d(mesh)
    ax.set_xlim(0, p.shape[0])
    ax.set_ylim(0, p.shape[1])
    ax.set_zlim(0, p.shape[2])
    plt.show()

打印函数有个阈值参数,来打印特定的结构,比如tissue或者骨头。400是一个仅仅打印骨头的阈值(HU对照表)

 
plot_3d(pix_resampled, 400)

dicom格式的图像

 
def plot_ct_scan(scan):
    '''
            plot a few more images of the slices
    :param scan:
    :return:
    '''
    f, plots = plt.subplots(int(scan.shape[0] / 20) + 1, 4, figsize=(50, 50))
    for i in range(0, scan.shape[0], 5):
        plots[int(i / 20), int((i % 20) / 5)].axis('off')
        plots[int(i / 20), int((i % 20) / 5)].imshow(scan[i], cmap=plt.cm.bone)

此方法的效果示例如下:

dicom格式的图像

下面的代码使用了pythonde 的图像形态学操作。具体可以参考python高级形态学操作

 
def get_segmented_lungs(im, plot=False):
    '''
    This funtion segments the lungs from the given 2D slice.
    '''
    if plot == True:
        f, plots = plt.subplots(8, 1, figsize=(5, 40))
    '''
    Step 1: Convert into a binary image.
    '''
    binary = im < 604
    if plot == True:
        plots[0].axis('off')
        plots[0].set_title('binary image')
        plots[0].imshow(binary, cmap=plt.cm.bone)
    '''
    Step 2: Remove the blobs connected to the border of the image.
    '''
    cleared = clear_border(binary)
    if plot == True:
        plots[1].axis('off')
        plots[1].set_title('after clear border')
        plots[1].imshow(cleared, cmap=plt.cm.bone)
    '''
    Step 3: Label the image.
    '''
    label_image = label(cleared)
    if plot == True:
        plots[2].axis('off')
        plots[2].set_title('found all connective graph')
        plots[2].imshow(label_image, cmap=plt.cm.bone)
    '''
    Step 4: Keep the labels with 2 largest areas.
    '''
    areas = [r.area for r in regionprops(label_image)]
    areas.sort()
    if len(areas) > 2:
        for region in regionprops(label_image):
            if region.area < areas[-2]:
                for coordinates in region.coords:
                       label_image[coordinates[0], coordinates[1]] = 0
    binary = label_image > 0
    if plot == True:
        plots[3].axis('off')
        plots[3].set_title(' Keep the labels with 2 largest areas')
        plots[3].imshow(binary, cmap=plt.cm.bone)
    '''
    Step 5: Erosion operation with a disk of radius 2. This operation is
    seperate the lung nodules attached to the blood vessels.
    '''
    selem = disk(2)
    binary = binary_erosion(binary, selem)
    if plot == True:
        plots[4].axis('off')
        plots[4].set_title('seperate the lung nodules attached to the blood vessels')
        plots[4].imshow(binary, cmap=plt.cm.bone)
    '''
    Step 6: Closure operation with a disk of radius 10. This operation is
    to keep nodules attached to the lung wall.
    '''
    selem = disk(10)
    binary = binary_closing(binary, selem)
    if plot == True:
        plots[5].axis('off')
        plots[5].set_title('keep nodules attached to the lung wall')
        plots[5].imshow(binary, cmap=plt.cm.bone)
    '''
    Step 7: Fill in the small holes inside the binary mask of lungs.
    '''
    edges = roberts(binary)
    binary = ndi.binary_fill_holes(edges)
    if plot == True:
        plots[6].axis('off')
        plots[6].set_title('Fill in the small holes inside the binary mask of lungs')
        plots[6].imshow(binary, cmap=plt.cm.bone)
    '''
    Step 8: Superimpose the binary mask on the input image.
    '''
    get_high_vals = binary == 0
    im[get_high_vals] = 0
    if plot == True:
        plots[7].axis('off')
        plots[7].set_title('Superimpose the binary mask on the input image')
        plots[7].imshow(im, cmap=plt.cm.bone)
    return im

此方法每个步骤对图像做不同的处理,依次为二值化、清除边界、连通区域标记、腐蚀操作、闭合运算、孔洞填充、效果如下:

dicom格式的图像

为了减少有问题的空间,我们可以分割肺部图像(有时候是附近的组织)。这包含一些步骤,包括区域增长和形态运算,此时,我们只分析相连组件。

步骤如下:

  • 阈值图像(-320HU是个极佳的阈值,但是此方法中不是必要)

  • 处理相连的组件,以决定当前患者的空气的标签,以1填充这些二值图像

  • 可选:当前扫描的每个轴上的切片,选定最大固态连接的组织(当前患者的肉体和空气),并且其他的为0。以掩码的方式填充肺部结构。

  • 只保留最大的气袋(人类躯体内到处都有气袋)

 
def largest_label_volume(im, bg=-1):
    vals, counts = np.unique(im, return_counts=True)
    counts = counts[vals != bg]
    vals = vals[vals != bg]
    if len(counts) > 0:
        return vals[np.argmax(counts)]
    else:
        return None
def segment_lung_mask(image, fill_lung_structures=True):
    # not actually binary, but 1 and 2.
    # 0 is treated as background, which we do not want
    binary_image = np.array(image > -320, dtype=np.int8)+1
    labels = measure.label(binary_image)
    # Pick the pixel in the very corner to determine which label is air.
    #   Improvement: Pick multiple background labels from around the patient
    #   More resistant to "trays" on which the patient lays cutting the air
    #   around the person in half
    background_label = labels[0,0,0]
    #Fill the air around the person
    binary_image[background_label == labels] = 2
    # Method of filling the lung structures (that is superior to something like
    # morphological closing)
    if fill_lung_structures:
        # For every slice we determine the largest solid structure
        for i, axial_slice in enumerate(binary_image):
            axial_slice = axial_slice - 1
            labeling = measure.label(axial_slice)
            l_max = largest_label_volume(labeling, bg=0)
            if l_max is not None: #This slice contains some lung
                binary_image[i][labeling != l_max] = 1
    binary_image -= 1 #Make the image actual binary
    binary_image = 1-binary_image # Invert it, lungs are now 1
    # Remove other air pockets insided body
    labels = measure.label(binary_image, background=0)
    l_max = largest_label_volume(labels, bg=0)
    if l_max is not None: # There are air pockets
        binary_image[labels != l_max] = 0
    return binary_image

查看切割效果

 
segmented_lungs = segment_lung_mask(pix_resampled, False)
segmented_lungs_fill = segment_lung_mask(pix_resampled, True)
plot_3d(segmented_lungs, 0)

dicom格式的图像

我们可以将肺内的结构也包含进来(结节是固体),不仅仅只是肺部内的空气

 
  1. plot_3d(segmented_lungs_fill, 0)
    
    dicom格式的图像

使用mask时,要注意首先进行形态扩充(python的skimage的skimage.morphology)操作(即使用圆形kernel,结节是球体),参考 python形态操作。这会在所有方向(维度)上扩充mask。仅仅肺部的空气+结构将不会包含所有结节,事实上有可能遗漏黏在肺部一侧的结节(这会经常出现,所以建议最好是扩充mask)。

归一化处理

当前的值范围是[-1024,2000]。而任意大于400的值并不是处理肺结节需要考虑,因为它们都是不同反射密度下的骨头。LUNA16竞赛中常用来做归一化处理的阈值集是-1000和400.以下代码

归一化

 
MIN_BOUND = -1000.0
MAX_BOUND = 400.0
def normalize(image):
    image = (image - MIN_BOUND) / (MAX_BOUND - MIN_BOUND)
    image[image>1] = 1.
    image[image<0] = 0.
    return image

0值中心化

简单来说就是所有像素值减去均值。LUNA16竞赛中的均值大约是0.25.

不要对每一张图像做零值中心化(此处像是在kernel中完成的)CT扫描器返回的是校准后的精确HU计量。不会出现普通图像中会出现某些图像低对比度和明亮度的情况

 
PIXEL_MEAN = 0.25
def zero_center(image):
    image = image - PIXEL_MEAN
    return image

归一化和零值中心化的操作主要是为了后续训练网络,零值中心化是网络收敛的关键。

mhd的数据只是格式与dicom不一样,其实质包含的都是病人扫描。处理MHD需要借助SimpleIKT这个包,处理思路详情可以参考Data Science Bowl2017的toturail Data Science Bowl 2017。需要注意的是MHD格式的数据没有HU值,它的值域范围与dicom很不同。

我们以LUNA2016年的数据处理流程为例。参考代码为 LUNA2016数据切割

 
import SimpleITK as sitk
import numpy as np
import csv
from glob import glob
import pandas as pd
file_list=glob(luna_subset_path+"*.mhd")
#####################
#
# Helper function to get rows in data frame associated
# with each file
def get_filename(case):
    global file_list
    for f in file_list:
        if case in f:
            return(f)
#
# The locations of the nodes
df_node = pd.read_csv(luna_path+"annotations.csv")
df_node["file"] = df_node["seriesuid"].apply(get_filename)
df_node = df_node.dropna()
#####
#
# Looping over the image files
#
fcount = 0
for img_file in file_list:
    print "Getting mask for image file %s" % img_file.replace(luna_subset_path,"")
    mini_df = df_node[df_node["file"]==img_file] #get all nodules associate with file
    if len(mini_df)>0:       # some files may not have a nodule--skipping those
        biggest_node = np.argsort(mini_df["diameter_mm"].values)[-1]   # just using the biggest node
        node_x = mini_df["coordX"].values[biggest_node]
        node_y = mini_df["coordY"].values[biggest_node]
        node_z = mini_df["coordZ"].values[biggest_node]
        diam = mini_df["diameter_mm"].values[biggest_node]

一直在寻找MHD格式数据的处理方法,对于dicom格式的CT有很多论文根据其HU值域可以轻易地分割肺、骨头、血液等,但是对于MHD没有这样的参考。从LUNA16论坛得到的解释是,LUNA16的MHD数据已经转换为HU值了,不需要再使用slope和intercept来做rescale变换了。此论坛主题下,有人提出MHD格式没有提供pixel spacing(mm) 和 slice thickness(mm) ,而标准文件annotation.csv文件中结节的半径和坐标都是mm单位,最后确认的是MHD格式文件中只保留了体素尺寸以及坐标原点位置,没有保存slice thickness。即,dicom才是原始数据格式。

MHD值的坐标体系是体素,以mm为单位(dicom的值是GV灰度值)。结节的位置是CT scanner坐标轴里面相对原点的mm值,需要将其转换到真实坐标轴位置,可以使用SimpleITK包中的 GetOrigin() ` GetSpacing()`。图像数据是以512x512数组的形式给出的。

坐标变换如下:

dicom格式的图像

相应的代码处理如下:

 
itk_img = sitk.ReadImage(img_file)
img_array = sitk.GetArrayFromImage(itk_img) # indexes are z,y,x (notice the ordering)
center = np.array([node_x,node_y,node_z])   # nodule center
origin = np.array(itk_img.GetOrigin())      # x,y,z  Origin in world coordinates (mm)
spacing = np.array(itk_img.GetSpacing())    # spacing of voxels in world coor. (mm)
v_center =np.rint((center-origin)/spacing)  # nodule center in voxel space (still x,y,z ordering)

在LUNA16的标注CSV文件中标注了结节中心的X,Y,Z轴坐标,但是实际取值的时候取的是Z轴最后三层的数组(img_array)

下述代码只提取了包含结节的最后三个slice的数据,代码参考自 LUNA_mask_extraction.py

 
i = 0
for i_z in range(int(v_center[2])-1,int(v_center[2])+2):
    mask = make_mask(center,diam,i_z*spacing[2]+origin[2],width,height,spacing,origin)
    masks[i] = mask
    imgs[i] = matrix2int16(img_array[i_z])
    i+=1
np.save(output_path+"images_%d.npy" % (fcount) ,imgs)
np.save(output_path+"masks_%d.npy" % (fcount) ,masks)

以下代码用于查看原始CT和结节mask。其实就是用matplotlib打印上一步存储的npy文件。

import matplotlib.pyplot as plt
imgs = np.load(output_path+'images_0.npy')
masks = np.load(output_path+'masks_0.npy')
for i in range(len(imgs)):
    print "image %d" % i
    fig,ax = plt.subplots(2,2,figsize=[8,8])
    ax[0,0].imshow(imgs[i],cmap='gray')
    ax[0,1].imshow(masks[i],cmap='gray')
    ax[1,0].imshow(imgs[i]*masks[i],cmap='gray')
    plt.show()
    raw_input("hit enter to cont : ")

示例结节和mask

接下来的处理和DICOM格式数据差不多,腐蚀膨胀、连通区域标记等。

参考信息:

灰度值是pixel value经过重重LUT转换得到的用来进行显示的值,而这个转换过程是不可逆的,也就是说,灰度值无法转换为ct值。只能根据窗宽窗位得到一个大概的范围。 pixel value经过modality lut得到Hu,但是怀疑pixelvalue的读取出了问题。dicom文件中存在(0028,0106)(0028,0107)两个tag,分别是最大最小pixel value,可以用来检验你读取的pixel value 矩阵是否正确。

LUT全称look up table,实际上就是一张像素灰度值的映射表,它将实际采样到的像素灰度值经过一定的变换如阈值、反转、二值化、对比度调整、线性变换等,变成了另外一 个与之对应的灰度值,这样可以起到突出图像的有用信息,增强图像的光对比度的作用。

参考:http://shartoo.github.io/medical_image_process/ 

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

常见的一些医疗图像处理步骤 的相关文章

  • 【华为OD机试】组成最大数(C++ Python Java)2023 B卷

    题目描述 小组中每位都有一张卡片 卡片上是6位内的正整数 将卡片连起来可以组成多种数字 计算组成的最大数字 输入描述 号分割的多个正整数字符串 不需要考虑非数字异常情况 小组最多25个人 输出描述 最大的数字字符串 用例1 输入 22 22
  • 在虚拟机上安装Ubuntu系统

    打开VMware 点击文件新建虚拟机 选择典型 下一步选择安装系统iso映像文件 安装位置我选择的E盘 保持默认 下一步 点击完成 运行系统后 上下左右键操作 选择中文 简体 回车 选择安装Ubuntu服务器版 语言选择中文 简体 选是 中
  • 字符集详解(一看就懂系列)

    原文请参考 https blog csdn net qq 28098067 article details 53486032 一 编码历史与区别 一直对字符的各种编码方式懵懵懂懂 什么ANSI UNICODE UTF 8 GB2312 GB
  • Qt图形视图框架:QGraphicsScene详解

    一 描述 1 场景提供了一个用于管理大量2D图形项的平面 该类充当图形项的容器 它与视图一起用于可视化2D曲面上的图形图形项 2 场景没有自己的视觉外观 只负责管理图形项 3 场景的最大优势之一就是其快速有效地定位图形项的能力 即使场景中有
  • React导入json数据

    本文提供两种方式 读者根据自己的需要进行选择 1 第一种方式 直接import json文件 这种方式依赖于 json loader模块 npm install json loader https www npmjs com package

随机推荐

  • gs104d键盘使用问题记录

    键盘官网说明 官网说明书 有线开关拨到off 无线拨到on 蓝牙连接 1 开关拨OFF 装电池 拨到ON 指示灯闪一下 只是闪一下不是常亮 2 FN加Q指示灯闪一下 空格右边FN按住再按住P长按 指示灯闪烁 松手 第一个设备打开蓝牙添加搜索
  • iOS开发Google Protocol Buffer 的使用(三)

    pragma mark 获取数据 void getData NSUserDefaults defaults NSUserDefaults standardUserDefaults NSString subServerUrl defaults
  • 使用vscode开发C51项目

    使用vscode开发C51项目 头文件包含 settings json中要加入51的头文件 C Cpp default includePath workspaceFolder C Keil v5 C51 INC 扩展类型问题 遇到问题 sb
  • 憨批的语义分割重制版3——Pytorch 搭建自己的PSPNet语义分割平台

    憨批的语义分割重制版3 Pytorch 搭建自己的PSPNet语义分割平台 学习前言 什么是PSPNet模型 代码下载 PSPNet实现思路 一 预测部分 1 主干网络介绍 2 加强特征提取结构 3 利用特征获得预测结果 二 训练部分 1
  • 【VQ-VAE-2论文精读】Generating Diverse High-Fidelity Images with VQ-VAE-2

    VQ VAE 2论文精读 Generating Diverse High Fidelity Images with VQ VAE 2 0 前言 Abstract 1 Introduction 2 Background 2 1 Vector
  • 【正点原子STM32连载】第二十四章 高级定时器PWM输入模式实验 摘自【正点原子】APM32F407最小系统板使用指南

    1 实验平台 正点原子stm32f103战舰开发板V4 2 平台购买地址 https detail tmall com item htm id 609294757420 3 全套实验源码 手册 视频下载地址 http www openedv
  • 清除TortoiseGit保存密码

    找到C Users Administrator目录下的 gitconfig文件 该文件为隐藏文件 解除隐藏后即可看到 删除 credential helper manager 参考链接 另一种方法清除密码 https www cnblogs
  • 2022年互联网行业的寒流

    最近很多小伙伴私信我说到2022年是他从业以来找工作最艰难的一次 企业各种卷 BAT等一线大厂释放人才 毕业等各种裁人政策不断 每年几百万人应届毕业生 包括985 211 研究生 博士 硕士等不断的涌入 小公司面临倒闭 大公司面临裁人让30
  • 《软件工程》第5章系统建模

    系统建模就是建立系统抽象模型的过程 其中每一个模型表示系统的一个不同的视角或观点 系统建模现在通常意味着在UML中的图类型基础上使用某种图形化的表示法表示系统 然而 也有可能要开发系统的形式化 数学 模型 通常将其作为详细的系统规格说明 在
  • Qt常用小部件

    常用小部件 QLabel 常用来显示文本 数字 图片 gif动图 新建桌面应用程序 项目名testQLabel 基类QWidget 类名Widget 勾选创建界面文件 类构造函数中添加如下代码 准备好一个图片及gif格式动图 ui gt s
  • cookie,session,token之间的关系

    今天和大家聊一下关于 Cookie Session Token 的那些事儿 这是我的一个读者朋友面试微信的实习岗位时遇到的 在此和大家分享一下 话不多说 直接开车 1 网站交互体验升级 作为网友的我们 每天都会使用浏览器来逛各种网站 来满足
  • nginx做yum源

    我这边环境是原先有个nginx只是做了代理转发 现在需要在通过nginx做yum源方便后期安装源 1 原先的配置代理转发 为不影响原先配置及端口 在http中最末尾加 include local nginx conf d conf 加载其它
  • (UI)Android自定义图片裁剪

    具体UI效果如下 思路 绘制5个rect 其中四个为半透明深色背景 一个为透明背景的裁剪内容框 之前也考虑过用region 但是自测的时候 发现两个region之间颜色会相互影响 可能是我代码问题 有了解的小伙伴可以指导一下哈 就用了5个R
  • jQuery 入门教程(36): jQuery UI Menu 示例

    jQuery Menu 组件可以应用到任何具有父 子关系的元素 就其变为菜单 但通常使用u gt li 如果你希望使用除 ul li 之外的元素 可以通过menus 来配置 下例使用缺省的 ui和 li 菜单支持选择事件select 因此可
  • YoloV5源码部分注释解读(ultralytics版本)(yolo.py)

    yolo py的主要作用是构建yolov5的模型 而且这个yolo py文件可以单独执行 这里主要对目标检测中的相关类进行了注释解读 分割等没有用到的暂时没有注释 第一部分 导入包 配置路径等 第二部分 程序入口 执行程序 在这部分中 创建
  • el-input输入框涉及到scope的校验问题

    需求描述 在el table中 对每一行数据的数量进行校验 对于数量要用el input输入框进行输入数值 校验主要涉及 每次输入的时候都要清空el input输入框的数值 输入值只能为数字 并且要对输入的数量进行判断是否超过库存的最大数量
  • php双写绕过,PHP preg_系列漏洞小结

    最近看 P 神以前写的文章 其中在 3 个参数的回调函数中提到了 preg replace e 命令执行 对这块不是很熟悉的我特此写这篇文章总结学习一下 preg matchint preg match string pattern str
  • 【C++】【python】【kafka】使用C++调用python函数向kafka发送消息

    1 python操作kafka的代码 import sys import time import json from kafka import KafkaProducer from kafka import KafkaConsumer fr
  • (数据结构)顺序表操作——C实现

    线性表的顺序表示指的是用一组地址连续的存储单元 内存 依次存储线性表的数据元素 特点 逻辑相邻 物理也相邻 支持存放 插入 删除 修改 读取数据等操作 一位数组是一种特殊的顺序表 但顺序表不是数组 插入和删除较链表来说不太方便 固定大小的顺
  • 常见的一些医疗图像处理步骤

    一 数据格式 1 1 dicom DICOM是医学图像中标准文件 这些文件包含了诸多的元数据信息 比如像素尺寸 每个维度的一像素代表真实世界里的长度 此处以kaggle Data Science Bowl 数据集为例 data scienc