最近做了一个关于分割直肠肿瘤的项目,用的是Unet模型,从一开始就遇到了许许多多的问题,比如读dcm图像的问题,训练结果全黑或全灰的问题,但功夫不负有心人,经过几个星期的调试,最终终于得到了还不错的结果。下面想给大家分享一下我的源码,并说明一下相关问题的解决方法。
我会分块对代码进行分析,为了大家调试方便,只需要将我的代码块从上到下连起来就可以正常运行了。

代码和数据集都在github和网盘上:https://github.com/zf617707527/Unet1.git

网盘:链接:https://pan.baidu.com/s/1ZtK6MEPozLC7K8BnSO9ncg

提取码:u4wg

所依赖的库

import cv2
from keras.preprocessing.image import ImageDataGenerator
import os
import numpy as np
from keras.models import Model
from keras.layers import Input, concatenate, Conv2D, MaxPooling2D, Conv2DTranspose, Cropping2D
from keras.optimizers import Adam
from keras.callbacks import ModelCheckpoint
from keras import backend as K
from skimage import io
import h5py
import SimpleITK as sitk

装库的时候也遇到了一些问题,我一般习惯于把pycharm和anaconda结合起来用,将anaconda的库配置到pycharm中,就可以省去很多装库的问题,可以参照我的这篇博客pycharm和anaconda的安装与配置
。但是有一些库还是很难装,经常安装失败,大家可以试一试翻墙后再用anaconda的命令行进行安装,我用这个方法还是挺有效的,或者使用清华源也是可以的。

所有的函数块

切片函数

def getRangImageDepth(image):firstflag = Truestartposition = 0endposition = 0for z in range(image.shape[0]):notzeroflag = np.max(image[z])if notzeroflag and firstflag:startposition = zfirstflag = Falseif notzeroflag:endposition = zreturn startposition, endposition

这个函数进行了切片操作,只对有肿瘤的CT图和相应的掩模图进行训练,减少其它无肿瘤图像对训练模型的影响。

读h5文件

class HDF5DatasetGenerator:def __init__(self, dbPath, batchSize, preprocessors=None,aug=None, binarize=True, classes=2):self.batchSize = batchSizeself.preprocessors = preprocessorsself.aug = augself.binarize = binarizeself.classes = classesself.db = h5py.File(dbPath)self.numImages = self.db["images"].shape[0]#print('&&&&&')#print(self.db["images"].shape[0])#print(self.db["images"].shape)#        self.numImages = totalprint("total images:", self.numImages)self.num_batches_per_epoch = int((self.numImages - 1) / batchSize) + 1def generator(self, shuffle=True, passes=np.inf):epochs = 0 #原来的值为0while epochs < passes:shuffle_indices = np.arange(self.numImages)shuffle_indices = np.random.permutation(shuffle_indices)for batch_num in range(self.num_batches_per_epoch):start_index = batch_num * self.batchSizeend_index = min((batch_num + 1) * self.batchSize, self.numImages)# h5py get item by index,参数为list,而且必须是增序batch_indices = sorted(list(shuffle_indices[start_index:end_index]))#print('******')#print(batch_indices)images = self.db["images"][batch_indices, :, :, :]labels = self.db["masks"][batch_indices, :, :, :]#                if self.binarize:#                    labels = np_utils.to_categorical(labels, self.classes)if self.preprocessors is not None:procImages = []for image in images:for p in self.preprocessors:image = p.preprocess(image)procImages.append(image)images = np.array(procImages)if self.aug is not None:# 不知道意义何在?本身images就有batchsize个了(images, labels) = next(self.aug.flow(images, labels,batch_size=self.batchSize))yield (images, labels)epochs += 1def close(self):self.db.close()

用来读h5文件的数据

写h5文件

class HDF5DatasetWriter:def __init__(self, image_dims, mask_dims, outputPath, bufSize=200):"""Args:- bufSize: 当内存储存了bufSize个数据时,就需要flush到外存"""if os.path.exists(outputPath):raise ValueError("The supplied 'outputPath' already""exists and cannot be overwritten. Manually delete""the file before continuing", outputPath)self.db = h5py.File(outputPath, "w")self.data = self.db.create_dataset("images", image_dims, dtype="float")self.masks = self.db.create_dataset("masks", mask_dims, dtype="int")self.bufSize = bufSizeself.buffer = {"data": [], "masks": []}self.idx = 0def add(self, rows, masks):# extend() 函数用于在列表末尾一次性追加另一个序列中的多个值(用新列表扩展原来的列表)# 注意,用extend还有好处,添加的数据不会是之前list的引用!!self.buffer["data"].extend(rows)self.buffer["masks"].extend(masks)print("len ", len(self.buffer["data"]))if len(self.buffer["data"]) >= self.bufSize:self.flush()def flush(self):i = self.idx + len(self.buffer["data"])self.data[self.idx:i, :, :, :] = self.buffer["data"]self.masks[self.idx:i, :, :, :] = self.buffer["masks"]print("h5py have writen %d data" % i)self.idx = iself.buffer = {"data": [], "masks": []}def close(self):if len(self.buffer["data"]) > 0:self.flush()self.db.close()return self.idx

用来写数据到h5文件

得到CT的hu值函数

def get_pixels_hu(scans):image = np.stack([ sitk.GetArrayFromImage(s)[0] for s in scans])return np.array(image, dtype=np.int16)

因为我们的CT图是用SimpleITK读的,所读出来的就是hu值,再这里只做了将读取的CT图聚合到一起的操作。

窗口函数

def transform_ctdata(self, windowWidth, windowCenter, normal=False):"""注意,这个函数的self.image一定得是float类型的,否则就无效!return: trucated image according to window center and window width"""minWindow = float(windowCenter) - 0.5 * float(windowWidth)print('<<<')print(self)newimg = (self - minWindow) / float(windowWidth)newimg[newimg < 0] = 0newimg[newimg > 1] = 0if not normal:newimg = (newimg * 255).astype('uint8')return newimg

这个函数是做了一个窗口化的操作,为了增强对比度,具体原理就是设置一个hu值的窗口,我们是统计直肠肿瘤的hu值大概确定了直肠肿瘤所处的hu值范围,然后将这个范围做成窗口,窗口内的也就是目标区域不变,其他区域都变成白色,使目标区域与周围其他组织对比度更大,增强学习的效果。选择一个合适的窗口是很关键的,如果窗口选的好,最后就算输入一个不含目标区域的图像,经过窗口化处理就会变成一个全白的图像,这样模型就同时具有了识别不含肿瘤CT的功能

直方图均衡化

def clahe_equalized(imgs,start,end):assert (len(imgs.shape)==3)  #3D arrays  assert用于声明该条件是对的,也即输入的图片是三通道的,如果不是会有一个报错!#create a CLAHE object (Arguments are optional).clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))#自适应的均衡化过程,这些参数都是固定的。imgs_equalized = np.empty(imgs.shape)for i in range(start, end+1):imgs_equalized[i,:,:] = clahe.apply(np.array(imgs[i,:,:], dtype = np.uint8))return imgs_equalized

主程序

数据增强

TOTAL = 600#训练集数目
VAL_TOTAL = 35#测试集数目
seed=42
data_gen_args = dict(rotation_range=3,width_shift_range=0.01,height_shift_range=0.01,shear_range=0.01,zoom_range=0.01,fill_mode='nearest')image_datagen = ImageDataGenerator(**data_gen_args)
mask_datagen = ImageDataGenerator(**data_gen_args)

其中的TOTAL是很重要的参数,这里的训练集数目要自己数出来,这里TOTAL还会影响到steps_per_epoch这个参数,因为这个参数设置的为TOTAL/BATCH_SIZE,决定了每一个epoch将训练集分成了多少份进行训练。但是经过我试验TOTAL并不一定要是训练集的数目,不一样也不会报错,而且会影响训练的效果,之前这个TOTAL设的不对导致结果总是全黑或全灰,而我把改到600时效果就很不错,所以这个参数是很重要的。

训练数据提取和数据预处理

dataset = HDF5DatasetWriter(image_dims=(TOTAL, 512, 512, 1),mask_dims=(TOTAL, 512, 512, 1),outputPath="train_liver.h5")  # 保存到文件中去
for i in range(1, 18):  # 前18个人作为训练集full_images = []  # 后面用来存储目标切片的列表full_livers = []  # 功能同上# 注意不同的系统,文件分割符的区别label_path = 'data1/%d/mask' %i        #读取掩模图片data_path = 'data1/%d/arterial phase' %i     #加入CT图片的位置liver_slices = [cv2.cvtColor((cv2.imread(label_path + '/' + s)),cv2.COLOR_BGR2GRAY)for s in os.listdir(label_path)]livers = np.stack([s for s in liver_slices])#所有的直肠的图片的矩阵聚合在一起!#print(data_path + '/' + os.listdir(data_path)[0])image_slices = [sitk.ReadImage(data_path + '/' + s) for s in os.listdir(data_path)]images = get_pixels_hu(image_slices)#对切片进行CT-HU值还原!images = transform_ctdata(images,90, 25)#增强对比度start, end = getRangImageDepth(livers)#得到有肿瘤的图像# print('(((')# print(start,end)images = clahe_equalized(images, start, end) #对所有的图片都进行了图片直方图的均衡化!images /= 255#像素值归为【0,1】,这样就利于1后续生成掩模图了total = end - start + 1#经过切片后图片的总数print("%d person, total slices %d" % (i, total))# 首和尾目标区域都太小,舍弃images = images[start:end]print("%d person, images.shape:(%d,)" % (i, images.shape[0]))livers[np.array(livers) > 0] = 1#对掩模图进行了归一化处理livers = livers[start:end]#为了与images匹配,所以进行了相同的切片操作!full_images.append(images)full_livers.append(livers)full_images = np.vstack(full_images) #也是叠加矩阵的作用full_images = np.expand_dims(full_images, axis=-1)  #expand_dims就是对矩阵的形状进行调制,axis=-1,表示对其进行了最后面的插入。full_livers = np.vstack(full_livers)full_livers = np.expand_dims(full_livers, axis=-1)# 数据增强!image_datagen.fit(full_images, augment=True, seed=seed)mask_datagen.fit(full_livers, augment=True, seed=seed)image_generator = image_datagen.flow(full_images, seed=seed)mask_generator = mask_datagen.flow(full_livers, seed=seed)train_generator = zip(image_generator, mask_generator)  # 将两个图片集进行了叠加x = []y = []i = 0for x_batch, y_batch in train_generator:i += 1x.append(x_batch)y.append(y_batch)if i >= 2:  # 因为我不需要太多的数据breakx = np.vstack(x)y = np.vstack(y)dataset.add(full_images, full_livers)dataset.add(x, y)
dataset.close()

测试数据提取和数据预处理

full_images2 = []
full_livers2 = []
for i in range(105,108):# 后3个人作为测试样本label_path = 'data1/%d/mask' %i      #读取掩模图片data_path = 'data1/%d/arterial phase' %i   #加入CT图片的位置liver_slices = [cv2.cvtColor((cv2.imread(label_path + '/' + s)),cv2.COLOR_BGR2GRAY)for s in os.listdir(label_path)]#listdir需要接受到的是一个绝对路径//它返回该路径下的所有文件及其目录# s.pixel_array 获取dicom格式中的像素值livers = np.stack([s for s in liver_slices])#所有的肝部的图片的矩阵聚合在一起!image_slices = [sitk.ReadImage(data_path+'/'+s) for s in os.listdir(data_path)]images = get_pixels_hu(image_slices)  # 对切片进行CT-HU值还原!images = transform_ctdata(images, 250, 75)start, end = getRangImageDepth(livers)# print(')))')# print(start,end)images = clahe_equalized(images, start, end)  # 对所有的图片都进行了图片直方图的均衡化!images /= 255  # 像素值归为【0,1】,这样就利于1后续生成掩模图了images = images[start:end]print("%d person, images.shape:(%d,)" % (i, images.shape[0]))livers[livers > 0] = 1livers = livers[start:end]  # 为了与images匹配,所以进行了相同的切片操作!full_images2.append(images)full_livers2.append(livers)full_images2 = np.vstack(full_images2)
full_images2 = np.expand_dims(full_images2, axis=-1)
full_livers2 = np.vstack(full_livers2)
full_livers2 = np.expand_dims(full_livers2, axis=-1)dataset = HDF5DatasetWriter(image_dims=(full_images2.shape[0], full_images2.shape[1], full_images2.shape[2], 1),mask_dims=(full_images2.shape[0], full_images2.shape[1], full_images2.shape[2], 1),outputPath="val_liver.h5")#这里的路径其实就是val_outputPathdataset.add(full_images2, full_livers2)print("total images in val ", dataset.close())K.set_image_data_format('channels_last')

Unet模型的构建

def dice_coef(y_true, y_pred):smooth = 1.y_true_f = K.flatten(y_true)y_pred_f = K.flatten(y_pred)intersection = K.sum(y_true_f * y_pred_f)return (2. * intersection + smooth) / (K.sum(y_true_f) + K.sum(y_pred_f) + smooth)def dice_coef_loss(y_true, y_pred):return -dice_coef(y_true, y_pred)def get_crop_shape(target, refer):# width, the 3rd dimensionprint(target.shape)print(refer._keras_shape)cw = (target._keras_shape[2] - refer._keras_shape[2])assert (cw >= 0)if cw % 2 != 0:cw1, cw2 = int(cw / 2), int(cw / 2) + 1else:cw1, cw2 = int(cw / 2), int(cw / 2)# height, the 2nd dimensionch = (target._keras_shape[1] - refer._keras_shape[1])assert (ch >= 0)if ch % 2 != 0:ch1, ch2 = int(ch / 2), int(ch / 2) + 1else:ch1, ch2 = int(ch / 2), int(ch / 2)return (ch1, ch2), (cw1, cw2)def get_unet():inputs = Input((IMG_HEIGHT, IMG_WIDTH, 1))conv1 = Conv2D(32, (3, 3), activation='relu', padding='same')(inputs)conv1 = Conv2D(32, (3, 3), activation='relu', padding='same')(conv1)pool1 = MaxPooling2D(pool_size=(2, 2))(conv1)conv2 = Conv2D(64, (3, 3), activation='relu', padding='same')(pool1)conv2 = Conv2D(64, (3, 3), activation='relu', padding='same')(conv2)pool2 = MaxPooling2D(pool_size=(2, 2))(conv2)conv3 = Conv2D(128, (3, 3), activation='relu', padding='same')(pool2)conv3 = Conv2D(128, (3, 3), activation='relu', padding='same')(conv3)pool3 = MaxPooling2D(pool_size=(2, 2))(conv3)conv4 = Conv2D(256, (3, 3), activation='relu', padding='same')(pool3)conv4 = Conv2D(256, (3, 3), activation='relu', padding='same')(conv4)pool4 = MaxPooling2D(pool_size=(2, 2))(conv4)conv5 = Conv2D(512, (3, 3), activation='relu', padding='same')(pool4)conv5 = Conv2D(512, (3, 3), activation='relu', padding='same')(conv5)up_conv5 = Conv2DTranspose(256, (2, 2), strides=(2, 2), padding='same')(conv5)ch, cw = get_crop_shape(conv4, up_conv5)crop_conv4 = Cropping2D(cropping=(ch, cw), data_format="channels_last")(conv4)up6 = concatenate([up_conv5, crop_conv4], axis=3)conv6 = Conv2D(256, (3, 3), activation='relu', padding='same')(up6)conv6 = Conv2D(256, (3, 3), activation='relu', padding='same')(conv6)up_conv6 = Conv2DTranspose(128, (2, 2), strides=(2, 2), padding='same')(conv6)ch, cw = get_crop_shape(conv3, up_conv6)crop_conv3 = Cropping2D(cropping=(ch, cw), data_format="channels_last")(conv3)up7 = concatenate([up_conv6, crop_conv3], axis=3)conv7 = Conv2D(128, (3, 3), activation='relu', padding='same')(up7)conv7 = Conv2D(128, (3, 3), activation='relu', padding='same')(conv7)up_conv7 = Conv2DTranspose(64, (2, 2), strides=(2, 2), padding='same')(conv7)ch, cw = get_crop_shape(conv2, up_conv7)crop_conv2 = Cropping2D(cropping=(ch, cw), data_format="channels_last")(conv2)up8 = concatenate([up_conv7, crop_conv2], axis=3)conv8 = Conv2D(64, (3, 3), activation='relu', padding='same')(up8)conv8 = Conv2D(64, (3, 3), activation='relu', padding='same')(conv8)up_conv8 = Conv2DTranspose(64, (2, 2), strides=(2, 2), padding='same')(conv8)ch, cw = get_crop_shape(conv1, up_conv8)crop_conv1 = Cropping2D(cropping=(ch, cw), data_format="channels_last")(conv1)up9 = concatenate([up_conv8, crop_conv1], axis=3)conv9 = Conv2D(32, (3, 3), activation='relu', padding='same')(up9)conv9 = Conv2D(32, (3, 3), activation='relu', padding='same')(conv9)conv10 = Conv2D(1, (1, 1), activation='sigmoid')(conv9)model = Model(inputs=[inputs], outputs=[conv10])model.compile(optimizer=Adam(lr=1e-5), loss=dice_coef_loss, metrics=[dice_coef])return model
IMG_WIDTH = 512
IMG_HEIGHT = 512
IMG_CHANNELS = 1
outputPath = 'train_liver.h5'
val_outputPath = 'val_liver.h5'
BATCH_SIZE = 8

BATCH_SIZE就是每个BATCH训练多少张图片,根据电脑性能调整,设的太大可能跑不出来,而且还会影响steps_per_epoch,会对训练结果产生影响,所以谨慎修改。

class UnetModel:def train_and_predict(self):reader = HDF5DatasetGenerator(dbPath=outputPath,batchSize=BATCH_SIZE)train_iter = reader.generator()test_reader = HDF5DatasetGenerator(dbPath=val_outputPath,batchSize=BATCH_SIZE)test_iter = test_reader.generator()for zf in range(int(VAL_TOTAL/BATCH_SIZE)+1):#如果不加这个循环,每次只能跑出BATCH_SIZE张图片fixed_test_images, fixed_test_masks = test_iter.__next__()model = get_unet()model_checkpoint = ModelCheckpoint('weights.h5', monitor='val_loss', save_best_only=True)model.fit_generator(train_iter, steps_per_epoch=TOTAL/BATCH_SIZE, verbose=1, epochs=20, shuffle=True,validation_data=(fixed_test_images, fixed_test_masks), callbacks=[model_checkpoint])print('-' * 30)print('Loading and preprocessing test data...')print('-' * 30)print('-' * 30)print('Loading saved weights...')print('-' * 30)model.load_weights('weights.h5')print('-' * 30)print('Predicting masks on test data...')print('-' * 30)#print(fixed_test_images.shape)imgs_mask_test = model.predict(fixed_test_images, verbose=1)np.save('imgs_mask_test.npy', imgs_mask_test)print('-' * 30)print('Saving predicted masks to files...')print('-' * 30)pred_dir = 'preds'if not os.path.exists(pred_dir):os.mkdir(pred_dir)i = 0print(imgs_mask_test.shape)for image in imgs_mask_test:image = (image[:, :, 0] * 255.).astype(np.uint8)gt = (fixed_test_masks[i, :, :, 0] * 255.).astype(np.uint8)ini = (fixed_test_images[i, :, :, 0] * 255.).astype(np.uint8)io.imsave(os.path.join(pred_dir, str(i+ zf*BATCH_SIZE) + '_ini.png'), ini)io.imsave(os.path.join(pred_dir, str(i+ zf*BATCH_SIZE) + '_pred.png'), image)io.imsave(os.path.join(pred_dir, str(i+ zf*BATCH_SIZE) + '_gt.png'), gt)i += 1reader.close()test_reader.close()unet = UnetModel()
unet.train_and_predict()

读dcm的问题

在这个项目中读dcm总是出现各种各样的问题,可能是这个项目给我们提供的dcm图片格式有点问题,导致用pydicom读的时候有些属性没有,而用其他dcm读却正常。所以最后我们采用了SimpleITK来读取,但是读出来和pydicom读出来的属性是不一样的,这一点需要注意,可以将两种方式读取出来的属性一一对应,将用pydicom读出来的变量转换成用SimpleITK读出来的变量,程序就能正常运行了。

数据说明

每个病人一个文件夹


里面有两个文件夹arterial phase放CT图,mask放掩模图


这是CT图的文件夹,都为dcm文件


这是掩模图的文件夹,都是png文件,注意相对应的CT图和掩模图再文件夹里的位置一定要对应。

结果展示


经过处理后的CT图



医生画出的掩模图



预测出的掩模图

个别部分的棱角可能不太明显,但是整体形状以及位置是很相近的,而且我们只用了18个人做训练集,这个预测的模型用的是准确率到0.8的模型,经过参数的调整还能达到更高,大家可以再进行调试,一定可以得出更好的结果。

鸣谢

这个代码的整体框架是借鉴医学图像分割 基于深度学习的肝脏肿瘤分割 实战(一)这篇博客的,这篇关于Unet分割肝脏肿瘤的文章也是很棒的,大家也可以进行参考。

Unet分割直肠肿瘤图像相关推荐

  1. UNet-肝脏肿瘤图像语义分割

    目录 一. 语义分割 二. 数据集 三. 数据增强 图像数据处理步骤 CT图像增强方法 :windowing方法 直方图均衡化 获取掩膜图像深度 在肿瘤CT图中提取肿瘤 保存肿瘤数据 四. 数据加载 ...

  2. 计算机毕业设计(1)-- 基于UNet的肝脏肿瘤分割

    基于UNet的肝脏肿瘤分割 文章地址:arxiv.org/pdf/1505.04597.pdf 1.UNet概述 ​ UNet是一种用于图像分割的深度学习网络架构,最初由Olaf Ronneberge ...

  3. (脑肿瘤分割笔记:五十七)基于3D注意力UNet的脑肿瘤分割与生存预测

    Title:Brain Tumor Segmentation and Survival Prediction using 3D Attention UNet 摘要: 本文采用了3D UNet的架构,并 ...

  4. [深度学习论文笔记]使用多模态MR成像分割脑肿瘤的HNF-Netv2

    HNF-Netv2 for Brain Tumor Segmentation using multi-modal MR Imaging 使用多模态MR成像分割脑肿瘤的HNF-Netv2 Publish ...

  5. 零基础基于U-Net网络实战眼底图像血管提取

    文章目录 1 前言 2 血管提取任务概述 3 U-Net架构简介 4 眼底图像血管分割代码 5 结果评估可视化(ROC曲线) 6 改进U-Net网络完成眼底图像血管提取任务思路 1 前言 本文基于U- ...

  6. 基于深度学习和无监督血管分割的OCTA图像中央凹血管区分割 -- ICASSP2021 阅读笔记

    文章目录 文章信息 标题 出处 关键词 任务背景 任务需求 背景 贡献 提出的方法 实验 数据集描述 OCTAGON3 sFAZDA TA 实验指标 小结 文章信息 标题 FOVEAL A V ASC ...

  7. 基于U-net的肝脏肿瘤分割实战(Pytorch实现)

    这是我去年本科毕业设计的一部分,去年使用tensorflow去实现的,这几天就随手用Pytorch做了一下实现.肝脏肿瘤的分割是医学影像分割里面比较重要的一部分,实现从人的CT或MRI影像中将肝脏区域 ...

  8. unet分割如何取其中一类_深度学习与医学图像处理 案例学习1——Unet肺部分割(CT图像)...

    内容引用自https://www.kaggle.com/toregil/a-lung-u-net-in-keras?select=2d_masks.zip #引入普通包 1 import os2 im ...

  9. Unet项目解析(6): 图像分块、整合 / 数据对齐、网络输出转成图像

    项目GitHub主页:https://github.com/orobix/retina-unet 参考论文:Retina blood vessel segmentation with a convol ...

最新文章

  1. java获取某日期之前/之后一天的日期(实现代码)
  2. java servlet是单例吗_关于java:为什么apache servlet是单例?
  3. bzoj3110树套树
  4. Memcached 学习---(4)Memcached 连接
  5. opencv 编译静态库
  6. NP、OSPF邻居邻接关系
  7. 短视频无水印解析网站源码
  8. jsp post中文乱码
  9. [转帖]华为变革史(下)
  10. UWB PDOA brief introduction
  11. 2020年创业风口:社交电商
  12. SEO面试题与面试攻略
  13. 同时拿到网易和微信的offer!!他是怎么做到的?
  14. CLV3十二肽修饰CdTe,CdTe/Zns,CdHgTe,CdTe/CdSe,CdTeSe/CdsS/,Mn:CdTeSe/CdS,CdTeSe/Cds,InAs/InP/ZnSe量子点
  15. Python实现照片里添加文字
  16. 解决在Windows10下没有权限修改hosts文件的问题
  17. android 访问usb摄像头,从Android中的webview访问外部USB摄像头
  18. AI之路(二)——关于统计学习(statistical learning)Part 1 概论
  19. RPG血腥僵尸感染像素恐怖游戏素材地图场景
  20. CSS Text文本

热门文章

  1. C#【控件篇】按Tab键可以跳转到下个控件(TabIndex、TabStop的设置)
  2. 使用 spring 集成 dbcp 数据库连接池到 Wowza 插件
  3. 视频教程-大数据搜索技术-大数据
  4. COUNTIF函数:
  5. pip安装.whl文件
  6. 大数据分析和人工智能科普
  7. STM32-HAL-SPI-读写W25Q128FV-JEDEC ID(1)
  8. 为什么需要开发X 波段带通滤波器
  9. 关于dva的put,put.resolve
  10. oracle+dbcc+checkdb,dbcc checkdb 修复数据库