目标:介绍如何对图像数据进行预处理使训练得到的神经网络模型尽可能小地被无关因素所影响。但与此同时,复杂的预处理过程可能导致训练效率的下降。为了减少预处理对于训练速度的影响,TensorFlow 提供了多线程处理输入数据的解决方案。

TFRecord 输入数据格式

TensorFlow 提供了一种统一的格式来存储数据(TFRecord)。TFRecord 文件中的数据都是通过 tf.train.Example Protocol Buffer 的格式存储的。

tf.train.Example的定义:

  1. message Example {
  2. Features features = 1;
  3. };
  4. message Features {
  5. map<string, Feature> feature = 1;
  6. };
  7. message Features {
  8. oneof kind {
  9. BytesList bytes_list = 1;
  10. FloatList float_list = 2;
  11. Int64List int64_list = 3;
  12. }
  13. };

train.Example 的数据结构比较简洁,包含了一个从属性名称到取值的字典。其中属性名称为一个字符串,属性的取值可以为字符串(BytesList),实数列表(FloatList)或者整数列表(Int64List)。

  1. %pylab inline
  2. import tensorflow as tf
  3. import numpy as np
  4. from tensorflow.examples.tutorials.mnist import input_data
  1. Populating the interactive namespace from numpy and matplotlib
  1. def __int64__feature(value):
  2. '''生成整数型的属性'''
  3. return tf.train.Feature(int64_list= tf.train.Int64List(value=[value]))
  4. def __bytes__feature(value):
  5. '''生成字符型的属性'''
  6. return tf.train.Feature(bytes_list= tf.train.BytesList(value=[value]))
  1. mnist = input_data.read_data_sets('E:/datasets/mnist/data', dtype= tf.uint8, one_hot= True)
  1. Extracting E:/datasets/mnist/data\train-images-idx3-ubyte.gz
  2. Extracting E:/datasets/mnist/data\train-labels-idx1-ubyte.gz
  3. Extracting E:/datasets/mnist/data\t10k-images-idx3-ubyte.gz
  4. Extracting E:/datasets/mnist/data\t10k-labels-idx1-ubyte.gz
  1. images = mnist.train.images
  2. # 训练数据所对应的正确答案,可以作为一个属性保存在 TFRecord 中
  3. labels = mnist.train.labels
  4. # 训练数据的图像分辨率,这可以作为 Example 中的一个属性
  5. pixels = images.shape[1]
  6. num_examples = mnist.train.num_examples
  1. # 输出 TFRecord 文件的地址
  2. filename = 'E:/datasets/mnist/output.tfrecords'
  3. # 创建一个 writer 来写 TFRecord 文件
  4. writer = tf.python_io.TFRecordWriter(filename)
  5. for index in range(num_examples):
  6. # 将图片矩阵转化为一个字符串
  7. image_raw = images[index].tostring()
  8. # 将一个样例转换为 Example Protocol Buffer,并将所有的信息写入到这个数据结构
  9. examle = tf.train.Example(features = tf.train.Features(feature={
  10. 'pixels': __int64__feature(pixels),
  11. 'label': __int64__feature(np.argmax(labels[index])),
  12. 'image_raw': __bytes__feature(image_raw)
  13. }))
  14. # 将一个 Example 写入 TFRecord 文件
  15. writer.write(examle.SerializeToString())
  16. writer.close()

以上程序将 MNIST 数据集中所有的训练数据存储到一个 TFRecord 文件中。当数据量很大时,也可以将数据写入到多个 TFRecord 文件。

读取 TFRecord 文件中的数据

  1. import tensorflow as tf
  2. # 创建一个 reader 来读取 TFRecord 文件中的样例
  3. reader = tf.TFRecordReader()
  4. # 创建一个队列来维护输入文件列表
  5. filename_queue = tf.train.string_input_producer(['E:/datasets/mnist/output.tfrecords'])
  6. # 从文件中读出一个样例。也可使用 read_up_to 函数一次性读取多个样例
  7. _, serialized_example = reader.read(filename_queue)
  8. # 解析读入的一个样例。如果需要解析多个样例,可以用 parse_example 函数
  9. features = tf.parse_single_example(serialized_example,
  10. features= {
  11. # TensorFlow 提供两种不同的属性解析方法。一种是 tf.FixedLenFeature,解析结果为一个 Tensor
  12. # 另一种方法为 tf.VarLenFeature,解析结果为 SparseTensor,用于处理稀疏数据
  13. # 解析数据的格式需要和写入数据的格式一致
  14. 'image_raw': tf.FixedLenFeature([], tf.string),
  15. 'pixels': tf.FixedLenFeature([], tf.int64),
  16. 'label': tf.FixedLenFeature([], tf.int64),
  17. })
  18. # tf.decode_raw 可以将字符串解析成图片对应的像素数组
  19. images = tf.decode_raw(features['image_raw'], tf.uint8)
  20. labels = tf.cast(features['label'], tf.int32)
  21. pixels = tf.cast(features['pixels'], tf.int32)
  22. sess = tf.Session()
  23. # 启动多线程
  24. coord = tf.train.Coordinator()
  25. threads = tf.train.start_queue_runners(sess= sess, coord= coord)
  26. # 每次运行可以读取 TFRecord 文件中的一个样例。当所有样例都读完之后,在此样例中程序会再重头读取
  27. for i in range(10):
  28. image, label, pixel = sess.run([images, labels, pixels])
  29. sess.close()
  1. INFO:tensorflow:Error reported to Coordinator: <class 'tensorflow.python.framework.errors_impl.CancelledError'>, Run call was cancelled
  2. INFO:tensorflow:Error reported to Coordinator: <class 'tensorflow.python.framework.errors_impl.CancelledError'>, Run call was cancelled

图像数据处理

通过对图像的预处理,可以尽量避免模型受到无关因素的影响。在大部分图像识别问题中,通过图像预处理过程可以提高模型的准确率。

图像编码处理

一张 RGB 色彩模式的图片可看作一个三维矩阵,矩阵的每一个数字表示图像上不同位置,不同颜色的亮度。然而图像在存储时并不是直接记录这些矩阵中的数字,而是记录经过压缩编码之后的结果。所要将一张图像还原成一个三维矩阵,需要解码的过程。TensorFlow 提供了对 jpeg 和 png 格式图像的编码/解码函数。

  1. import matplotlib.pyplot as plt
  2. import tensorflow as tf
  3. import numpy as np
  1. # 读取图像的原始数据
  2. image_raw_data = tf.gfile.FastGFile('E:/datasets/cat.jpg', 'rb').read() # 必须是 ‘rb’ 模式打开,否则会报错
  1. with tf.Session() as sess:
  2. # 将图像使用 jpeg 的格式解码从而得到图像对应的三维矩阵
  3. # tf.image.decode_jpeg 函数对 png 格式的图像进行解码。解码之后的结果为一个张量,
  4. ## 在使用它的取值之前需要明确调用运行的过程。
  5. img_data = tf.image.decode_jpeg(image_raw_data)
  6. # 输出解码之后的三维矩阵。
  7. print(img_data.eval())
  1. [[[162 161 140]
  2. [162 162 138]
  3. [161 161 137]
  4. ...,
  5. [106 140 46]
  6. [101 137 47]
  7. [102 141 52]]
  8. [[164 162 139]
  9. [163 161 136]
  10. [163 161 138]
  11. ...,
  12. [104 138 43]
  13. [102 139 46]
  14. [108 138 50]]
  15. [[165 163 140]
  16. [165 163 138]
  17. [163 161 136]
  18. ...,
  19. [104 135 41]
  20. [102 137 43]
  21. [108 139 45]]
  22. ...,
  23. [[207 200 181]
  24. [206 199 180]
  25. [206 199 180]
  26. ...,
  27. [109 84 53]
  28. [107 84 53]
  29. [106 81 50]]
  30. [[205 200 180]
  31. [205 200 180]
  32. [206 199 180]
  33. ...,
  34. [106 83 49]
  35. [105 82 51]
  36. [106 81 50]]
  37. [[205 200 180]
  38. [205 198 179]
  39. [205 198 179]
  40. ...,
  41. [108 86 49]
  42. [105 82 48]
  43. [104 81 49]]]

打印图片

  1. with tf.Session() as sess:
  2. plt.imshow(img_data.eval())
  3. plt.show()

  1. with tf.Session() as sess:
  2. # 数据类型转换为实数方便程序对图像进行处理
  3. img_data = tf.image.convert_image_dtype(img_data, dtype= tf.float32)
  4. img_data = tf.image.convert_image_dtype(img_data, dtype= tf.uint8)
  5. # 将表示一张图片的三维矩阵重新按照 jpeg 格式编码并存入文件中
  6. ## 打开这张图像,可以得到和原始图像一样的图像
  7. encoded_image = tf.image.encode_jpeg(img_data)
  8. with tf.gfile.GFile('E:/datasets/output.jpg', 'wb') as f:
  9. f.write(encoded_image.eval())

重新调整图片大小

一般地,网上获取的图片的大小是不固定的,但是神经网络输入节点的个数是固定的。所以在将图像的像素作为输入提供给神经网络之前,需要将图片的大小统一。图片的大小调整有两种方式:

  1. 通过算法使得新的图片尽可能的保存原始图像上的所有信息。

    • Tensorflow 提供了四种不同的方法,并且将它们封装在 tf.image.resize_images 函数中。
  2. 裁剪或填充
保存完整信息
  1. with tf.Session() as sess:
  2. resized = tf.image.resize_images(img_data, [300, 300], method=0)
  3. # TensorFlow的函数处理图片后存储的数据是float32格式的,需要转换成uint8才能正确打印图片。
  4. print("Digital type: ", resized.dtype)
  5. print("Digital shape: ", resized.get_shape())
  6. cat = np.asarray(resized.eval(), dtype='uint8')
  7. # tf.image.convert_image_dtype(rgb_image, tf.float32)
  8. plt.imshow(cat)
  9. plt.show()
  1. Digital type: <dtype: 'float32'>
  2. Digital shape: (300, 300, 3)

tf.image.resize_imagesmethod 参数:

Method 取值 图像大小调整算法
0 双线性插值法(Bilinear interpolation)
1 最近邻居法(Nearest neighbor interpolation)
2 双三次插值法(Bicubic interpolation)
3 面积插值法(Area interpolation)
裁剪和填充

tf.image.resize_image_with_crop_or_pad 函数可以调整图像的大小。如果原始图像的尺寸大于目标图像这个函数会自动裁取原始图像中居中的部分;如果目标图像大于原始图像,这个函数会自动在原始图像的四周填充全 \(0\) 背景。

  1. with tf.Session() as sess:
  2. croped = tf.image.resize_image_with_crop_or_pad(img_data, 1000, 1000)
  3. padded = tf.image.resize_image_with_crop_or_pad(img_data, 3000, 3000)
  4. plt.imshow(croped.eval())
  5. plt.show()
  6. plt.imshow(padded.eval())
  7. plt.show()



通过比例调整图片大小

tf.image.central_crop 函数可以按比例裁剪图像。其中比例取值:\((0, 1]\) 的实数。

  1. with tf.Session() as sess:
  2. central_cropped = tf.image.central_crop(img_data, 0.5)
  3. plt.imshow(central_cropped.eval())
  4. plt.show()

以上的函数都是截取或填充图像的中间部分。使用 tf.image.crop_to_bounding_boxtf.image.pad_to_bounding_box 函数可以裁剪或填充给定区域的图像。

翻转图片

  1. with tf.Session() as sess:
  2. # 上下翻转
  3. flipped1 = tf.image.flip_up_down(img_data)
  4. plt.imshow(flipped1.eval())
  5. plt.show()
  6. # 左右翻转
  7. flipped2 = tf.image.flip_left_right(img_data)
  8. plt.imshow(flipped2.eval())
  9. plt.show()
  10. #对角线翻转
  11. transposed = tf.image.transpose_image(img_data)
  12. plt.imshow(transposed.eval())
  13. plt.show()





在很多图像识别问题中,图像的翻转不会影响识别结果。于是在训练图像识别的神经网络时,可以随机地翻转训练图像,这样训练得到的模型可以识别不同角度的实体。因而随机翻转训练图像是一种零成本的很常用的图像预处理方式。TensorFlow 提供了方便的 API 完成随机图像翻转的过程。以下代码实现了这个过程:

  1. with tf.Session() as sess:
  2. # 以一定概率上下翻转图片。
  3. flipped = tf.image.random_flip_up_down(img_data)
  4. plt.imshow(flipped.eval())
  5. plt.show()
  6. # 以一定概率左右翻转图片。
  7. flipped = tf.image.random_flip_left_right(img_data)
  8. plt.imshow(flipped.eval())
  9. plt.show()



图片色彩调整

和图像翻转类似,调整图像的亮度、对比度、饱和度和色相在很多图像识别应用中都不会影响识别结果。所以在训练神经网络模型时,可以随机调整训练图像的这些属性,从而使训练得到的模型尽可能小的受到无关因素的影响。以下代码可以完成此功能:

  1. with tf.Session() as sess:
  2. # 将图片的亮度 -0.5。
  3. adjusted = tf.image.adjust_brightness(img_data, -0.5)
  4. plt.imshow(adjusted.eval())
  5. plt.show()
  6. # 将图片的亮度 +0.5
  7. adjusted = tf.image.adjust_brightness(img_data, 0.5)
  8. plt.imshow(adjusted.eval())
  9. plt.show()
  10. # 在[-max_delta, max_delta)的范围随机调整图片的亮度。
  11. adjusted = tf.image.random_brightness(img_data, max_delta=0.5)
  12. plt.imshow(adjusted.eval())
  13. plt.show()





  1. with tf.Session() as sess:
  2. # 将图片的对比度 -5
  3. adjusted = tf.image.adjust_contrast(img_data, -5)
  4. plt.imshow(adjusted.eval())
  5. plt.show()
  6. # 将图片的对比度 +5
  7. adjusted = tf.image.adjust_contrast(img_data, 5)
  8. plt.imshow(adjusted.eval())
  9. plt.show()
  10. # 在[lower, upper]的范围随机调整图的对比度。
  11. lower = 7
  12. upper = 88
  13. adjusted = tf.image.random_contrast(img_data, lower, upper)
  14. plt.imshow(adjusted.eval())
  15. plt.show()





添加色相和饱和度

  1. with tf.Session() as sess:
  2. '''调节色相'''
  3. adjusted = tf.image.adjust_hue(img_data, 0.1)
  4. plt.imshow(adjusted.eval())
  5. plt.show()
  6. adjusted = tf.image.adjust_hue(img_data, 0.3)
  7. plt.imshow(adjusted.eval())
  8. plt.show()
  9. adjusted = tf.image.adjust_hue(img_data, 0.6)
  10. plt.imshow(adjusted.eval())
  11. plt.show()
  12. adjusted = tf.image.adjust_hue(img_data, 0.9)
  13. plt.imshow(adjusted.eval())
  14. plt.show()
  15. # 在[-max_delta, max_delta]的范围随机调整图片的色相。max_delta的取值在[0, 0.5]之间。
  16. adjusted = tf.image.random_hue(img_data, 0.5)
  17. plt.imshow(adjusted.eval())
  18. plt.show()









  1. with tf.Session() as sess:
  2. # 将图片的饱和度-5。
  3. adjusted = tf.image.adjust_saturation(img_data, -5)
  4. plt.imshow(adjusted.eval())
  5. plt.show()
  6. # 将图片的饱和度+5。
  7. adjusted = tf.image.adjust_saturation(img_data, 5)
  8. plt.imshow(adjusted.eval())
  9. plt.show()
  10. # 在[lower, upper]的范围随机调整图的饱和度。
  11. #adjusted = tf.image.random_saturation(img_data, lower, upper)



  1. with tf.Session() as sess:
  2. # 将代表一张图片的三维矩阵中的数字均值变为0,方差变为1。
  3. image = img.imread('E:/datasets/cat.jpg')
  4. adjusted = tf.image.per_image_standardization(image)
  5. cat = np.asarray(adjusted.eval(), dtype='uint8')
  6. plt.imshow(cat) # imshow 仅支持 uint8 格式
  7. plt.show()

标准化处理可以使得不同的特征具有相同的尺度(Scale)。这样,在使用梯度下降法学习参数的时候,不同特征对参数的影响程度就一样了。tf.image.per_image_standardization(image),此函数的运算过程是将整幅图片标准化(不是归一化),加速神经网络的训练。主要有如下操作,\(\frac{x - mean}{adjusted\_stddev}\),其中\(x\)为图片的 RGB 三通道像素值,\(mean\)分别为三通道像素的均值,\(adjusted\_stddev = \max \left(stddev, \frac{1.0}{\sqrt{image.NumElements()}}\right)\)。

- \(stddev\)为三通道像素的标准差,image.NumElements()计算的是三通道各自的像素个数。

  1. import tensorflow as tf
  2. import matplotlib.image as img
  3. import matplotlib.pyplot as plt
  4. import numpy as np
  5. sess = tf.InteractiveSession()
  6. image = img.imread('E:/datasets/cat.jpg')
  7. shape = tf.shape(image).eval()
  8. h,w = shape[0],shape[1]
  9. standardization_image = tf.image.per_image_standardization(image) #标准化
  10. fig = plt.figure()
  11. fig1 = plt.figure()
  12. ax = fig.add_subplot(111)
  13. ax.set_title('orginal image')
  14. ax.imshow(image)
  15. ax1 = fig1.add_subplot(311)
  16. ax1.set_title('original hist')
  17. ax1.hist(sess.run(tf.reshape(image,[h*w,-1])))
  18. ax1 = fig1.add_subplot(313)
  19. ax1.set_title('standardization hist')
  20. ax1.hist(sess.run(tf.reshape(standardization_image,[h*w,-1])))
  21. plt.ion()
  22. plt.show()



添加标注框并裁减

在很多图像识别数据集中,图像中需要关注的物体通常会被标注框圈出来。使用 tf.image.draw_bounding_boxes 函数便可以实现。

  1. with tf.Session() as sess:
  2. # 将图像缩小一些,这样可视化能够让标注框更加清晰
  3. img_data = tf.image.resize_images(img_data, [180, 267], method= 1)
  4. # tf.image.draw_bounding_boxes 函数要求图像矩阵中的数字为实数类型。
  5. # tf.image.draw_bounding_boxes 函数的输入是一个 batch 的数据,也就是多张图像组成的四维矩阵,所以需要将解码后的图像加一维
  6. batched = tf.expand_dims(tf.image.convert_image_dtype(img_data, tf.float32), 0)
  7. # 给出每张图像的所有标注框。一个标注框有四个数字,分别代表 [y_min, x_min, y_max, x_max]
  8. ## 注意这里给出的数字都是图像的相对位置。比如在 180 * 267 的图像中 [0.35, 0.47, 0.5, 0.56] 代表了从 (63, 125) 到 (90, 150)
  9. ### 的图像。
  10. boxes = tf.constant([[[0.05, 0.05, 0.9, 0.7], [0.35, 0.47, 0.5, 0.56]]])
  11. result = tf.image.draw_bounding_boxes(batched, boxes)
  12. plt.imshow(result[0].eval())
  13. plt.show()

和随机翻转图像、随机调整颜色类似,随机截取图像上有信息含量的部分也是一个提高模型健壮性(robustness)的一种方式。这样可以使训练得到的模型不受被识别物体的大小的影响。tf.image.sample_distorted_bounding_box 函数可以完成随机截取图像的过程。

  1. with tf.Session() as sess:
  2. boxes = tf.constant([[[0.05, 0.05, 0.9, 0.7], [0.35, 0.47, 0.5, 0.56]]])
  3. # 可以通过提供随机标注框的方式来告诉随机截取图像的算法哪些部分是 “有信息量” 的。
  4. begin, size, bbox_for_draw = tf.image.sample_distorted_bounding_box(
  5. tf.shape(img_data), bounding_boxes=boxes)
  6. # 通过标注框可视化随机截取得到的图像
  7. batched = tf.expand_dims(tf.image.convert_image_dtype(img_data, tf.float32), 0)
  8. image_with_box = tf.image.draw_bounding_boxes(batched, bbox_for_draw)
  9. plt.imshow(image_with_box[0].eval())
  10. plt.show()
  11. # 截取随机出来的图像,因为算法带有随机成分,所以每次的结果都可能不相同
  12. distorted_image = tf.slice(img_data, begin, size)
  13. plt.imshow(distorted_image.eval())
  14. plt.show()

TensorFlow 处理图片的更多相关文章

  1. TensorFlow-谷歌深度学习库 图片处理模块

    Module: tf.image 这篇文章主要介绍TensorFlow处理图片这一块,这个模块和之前说过的文件I/O处理一样也是主要从python导过来的. 通过官方文档,我们了解到这个模块主要有一下 ...

  2. 【小白学PyTorch】17 TFrec文件的创建与读取

    [新闻]:机器学习炼丹术的粉丝的人工智能交流群已经建立,目前有目标检测.医学图像.时间序列等多个目标为技术学习的分群和水群唠嗑的总群,欢迎大家加炼丹兄为好友,加入炼丹协会.微信:cyx64501661 ...

  3. AlexNet 网络详解及Tensorflow实现源码

    版权声明:本文为博主原创文章,未经博主允许不得转载. 1. 图片数据处理 2. 卷积神经网络 2.1. 卷积层 2.2. 池化层 2.3. 全链层 3. AlexNet 4. 用Tensorflow搭 ...

  4. TensorFlow-Bitcoin-Robot:一个基于 TensorFlow LSTM 模型的 Bitcoin 价格预测机器人

    简介 TensorFlow-Bitcoin-Robot:一个基于 TensorFlow LSTM 模型的 Bitcoin 价格预测机器人. 文章包括一下几个部分: 1.为什么要尝试做这个项目? 2.为 ...

  5. 【学习笔记】tensorflow图片读取

    目录 图像基本概念 图像基本操作 图像基本操作API 图像读取API 狗图片读取 CIFAR-10二进制数据读取 TFRecords TFRecords存储 TFRecords读取方法 图像基本概念 ...

  6. TensorFlow训练MNIST数据集(1) —— softmax 单层神经网络

    1.MNIST数据集简介 首先通过下面两行代码获取到TensorFlow内置的MNIST数据集: from tensorflow.examples.tutorials.mnist import inp ...

  7. Tensorflow图像处理

    Tensorflow图像处理主要包括:调整尺寸,图像翻转,调整色彩,处理标注框. 代码如下: #coding=utf-8 import matplotlib.pyplot as plt import ...

  8. 使用tensorflow深度学习识别验证码

    除了传统的PIL包处理图片,然后用pytessert+OCR识别意外,还可以使用tessorflow训练来识别验证码. 此篇代码大部分是转载的,只改了很少地方. 代码是运行在linux环境,tesso ...

  9. TensorFlow 图像预处理(一) 图像编解码,图像尺寸调整

    from: https://blog.csdn.net/chaipp0607/article/details/73029923 TensorFlow提供了几类图像处理函数,下面介绍图像的编码与解码,图 ...

随机推荐

  1. JS框架设计读书笔记之-节点模块

    节点的创建 浏览器提供了多种手段创建API,从流行程度依次是document.createElement.innerHTML.insertAdjacentHTML.createContextualFr ...

  2. java使用for循环做猜数字游戏

    package org.llh.test;import java.util.Random;import java.util.Scanner;/** * 猜数字游戏 *  * @author llh * ...

  3. Entropy

    Entropy Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Submi ...

  4. android 人脸检测你一定会遇到的坑

    笔者今年做了一个和人脸有关的android产品,主要是获取摄像头返回的预览数据流,判断该数据流是否包含了人脸,有人脸时显示摄像头预览框,无人脸时摄像头预览框隐藏,看上去这个功能并不复杂,其实在开发过程 ...

  5. JSP技术介绍

    1. 技术介绍 JSP即Java Server Page,中文全称是Java服务器语言.它是由Sun Microsystems公司倡导.许多公司参与建立的一种动态网页技术标准,它在动态网页的建设中有强 ...

  6. Asp.Net Core API网关Ocelot

    首先,让我们简单了解下什么是API网关? API网关是一个服务器,是系统的唯一入口.从面向对象设计的角度看,它与外观模式类似.API网关封装了系统内部架构,为每个客户端提供一个定制的API.它可能还具 ...

  7. Scanner扫描器

    扫描器 : Scanner 接收用户在键盘上的输入内容 是Java自带的一个工具,但是默认情况下没有在我们写的程序中使用三个步骤:1. 导入扫描器 : 导入类文件      import java.u ...

  8. vimgdb安装以及使用

    vimgdb安装 vim-7.3.tar.bz2http://www.vim.org/sources.phpvimgdb-for-vim7.3 (this patch) https://github. ...

  9. Python之signal模块

    http://www.cnblogs.com/dkblog/archive/2011/03/07/1980636.html 1.超时处理 #!/usr/bin/env python2.7 #-*- c ...

  10. MSSQL-并发控制-1-Transaction

         MSSQL并发控制原先打算分为两个部分写:隔离级别及锁,写的过程中,发现需要提及下事务的相关内容,故加多一篇博文,共3篇.         如果转载,请注明博文来源: www.cnblogs ...