做者:冯牮html
需求很容易描述清楚,如上图,就是在一张图里,把矩形形状的文档的四个顶点的坐标找出来。python
Google 搜索 opencv scan document,是能够找到好几篇相关的教程的,这些教程里面的技术手段,也都大同小异,关键步骤就是调用 OpenCV 里面的两个函数,cv2.Canny() 和 cv2.findContours()。git
看上去很容易就能实现出来,可是真实状况是,这些教程,仅仅是个 demo 演示而已,用来演示的图片,都是最理想的简单状况,真实的场景图片会比这个复杂的多,会有各类干扰因素,调用 canny 函数获得的边缘检测结果,也会比 demo 中的状况凌乱的多,好比会检测出不少各类长短的线段,或者是文档的边缘线被截断成了好几条短的线段,线段之间还存在距离不等的空隙。另外,findContours 函数也只能检测闭合的多边形的顶点,可是并不能确保这个多边形就是一个合理的矩形。所以在咱们的初版技术方案中,对这两个关键步骤,进行了大量的改进和调优,归纳起来就是:github
下面这张图表,可以很好的说明上面列出的这两个问题:算法
这张图表的第一列是输入的 image,最后的三列(先不用看这张图表的第二列),是用三组不一样阀值参数调用 canny 函数和额外的函数后获得的输出 image,能够看到,边缘检测的效果,并不老是很理想的,有些场景中,矩形的边,出现了很严重的断裂,有些边,甚至被彻底擦除掉了,而另外一些场景中,又会检测出不少干扰性质的长短边。可想而知,想用一个数学模型,适应这么不规则的边缘图,会是多么困难的一件事情。spring
在初版的技术方案中,负责的同窗花费了大量的精力进行各类调优,终于取得了还不错的效果,可是,就像前面描述的那样,仍是会遇到检测不出来的场景。在初版技术方案中,遇到这种状况的时候,采用的作法是针对这些不能检测的场景,人工进行分析和调试,调整已有的一组阀值参数和算法,可能还须要加入一些其余的算法流程(可能还会引入新的一些阀值参数),而后再整合到原有的代码逻辑中。通过若干轮这样的调整后,咱们发现,已经进入一个瓶颈,按照这种手段,很难进一步提升检测效果了。shell
既然传统的算法手段已经到极限了,那不如试试机器学习/神经网络。api
首先想到的,就是仿照人脸对齐(face alignment)的思路,构建一个端到端(end-to-end)的网络,直接回归拟合,也就是让这个神经网络直接输出 4 个顶点的坐标,可是,通过尝试后发现,根本拟合不出来。后来仔细琢磨了一下,以为不能直接拟合也是对的,由于:微信
后来还尝试过用 YOLO 网络作 Object Detection,用 FCN 网络作像素级的 Semantic Segmentation,可是结果都很不理想,好比:网络
前面尝试的几种神经网络算法,都不能获得想要的效果,后来换了一种思路,既然传统的技术手段里包含了两个关键的步骤,那能不能用神经网络来分别改善这两个步骤呢,通过分析发现,能够尝试用神经网络来替换 canny 算法,也就是用神经网络来对图像中的矩形区域进行边缘检测,只要这个边缘检测可以去除更多的干扰因素,那第二个步骤里面的算法也就能够变得更简单了。
按照这种思路,对于神经网络部分,如今的需求变成了上图所示的样子。
边缘检测这种需求,在图像处理领域里面,一般叫作 Edge Detection 或 Contour Detection,按照这个思路,找到了 Holistically-Nested Edge Detection 网络模型。
HED 网络模型是在 VGG16 网络结构的基础上设计出来的,因此有必要先看看 VGG16。
上图是 VGG16 的原理图,为了方便从 VGG16 过渡到 HED,咱们先把 VGG16 变成下面这种示意图:
在上面这个示意图里,用不一样的颜色区分了 VGG16 的不一样组成部分。
从示意图上能够看到,绿色表明的卷积层和红色表明的池化层,能够很明显的划分出五组,上图用紫色线条框出来的就是其中的第三组。
HED 网络要使用的就是 VGG16 网络里面的这五组,后面部分的 fully connected 层和 softmax 层,都是不须要的,另外,第五组的池化层(红色)也是不须要的。
去掉不须要的部分后,就获得上图这样的网络结构,由于有池化层的做用,从第二组开始,每一组的输入 image 的长宽值,都是前一组的输入 image 的长宽值的一半。
HED 网络是一种多尺度多融合(multi-scale and multi-level feature learning)的网络结构,所谓的多尺度,就是如上图所示,把 VGG16 的每一组的最后一个卷积层(绿色部分)的输出取出来,由于每一组获得的 image 的长宽尺寸是不同的,因此这里还须要用转置卷积(transposed convolution)/反卷积(deconv)对每一组获得的 image 再作一遍运算,从效果上看,至关于把第二至五组获得的 image 的长宽尺寸分别扩大 2 至 16 倍,这样在每一个尺度(VGG16 的每一组就是一个尺度)上获得的 image,都是相同的大小了。
把每个尺度上获得的相同大小的 image,再融合到一块儿,这样就获得了最终的输出 image,也就是具备边缘检测效果的 image。
基于 TensorFlow 编写的 HED 网络结构代码以下:
def hed_net(inputs, batch_size): # ref https://github.com/s9xie/hed/blob/master/examples/hed/train_val.prototxt with tf.variable_scope('hed', 'hed', [inputs]): with slim.arg_scope([slim.conv2d, slim.fully_connected], activation_fn=tf.nn.relu, weights_initializer=tf.truncated_normal_initializer(0.0, 0.01), weights_regularizer=slim.l2_regularizer(0.0005)): # vgg16 conv && max_pool layers net = slim.repeat(inputs, 2, slim.conv2d, 12, [3, 3], scope='conv1') dsn1 = net net = slim.max_pool2d(net, [2, 2], scope='pool1') net = slim.repeat(net, 2, slim.conv2d, 24, [3, 3], scope='conv2') dsn2 = net net = slim.max_pool2d(net, [2, 2], scope='pool2') net = slim.repeat(net, 3, slim.conv2d, 48, [3, 3], scope='conv3') dsn3 = net net = slim.max_pool2d(net, [2, 2], scope='pool3') net = slim.repeat(net, 3, slim.conv2d, 96, [3, 3], scope='conv4') dsn4 = net net = slim.max_pool2d(net, [2, 2], scope='pool4') net = slim.repeat(net, 3, slim.conv2d, 192, [3, 3], scope='conv5') dsn5 = net # net = slim.max_pool2d(net, [2, 2], scope='pool5') # no need this pool layer # dsn layers dsn1 = slim.conv2d(dsn1, 1, [1, 1], scope='dsn1') # no need deconv for dsn1 dsn2 = slim.conv2d(dsn2, 1, [1, 1], scope='dsn2') deconv_shape = tf.pack([batch_size, const.image_height, const.image_width, 1]) dsn2 = deconv_mobile_version(dsn2, 2, deconv_shape) # deconv_mobile_version can work on mobile dsn3 = slim.conv2d(dsn3, 1, [1, 1], scope='dsn3') deconv_shape = tf.pack([batch_size, const.image_height, const.image_width, 1]) dsn3 = deconv_mobile_version(dsn3, 4, deconv_shape) dsn4 = slim.conv2d(dsn4, 1, [1, 1], scope='dsn4') deconv_shape = tf.pack([batch_size, const.image_height, const.image_width, 1]) dsn4 = deconv_mobile_version(dsn4, 8, deconv_shape) dsn5 = slim.conv2d(dsn5, 1, [1, 1], scope='dsn5') deconv_shape = tf.pack([batch_size, const.image_height, const.image_width, 1]) dsn5 = deconv_mobile_version(dsn5, 16, deconv_shape) # dsn fuse dsn_fuse = tf.concat(3, [dsn1, dsn2, dsn3, dsn4, dsn5]) dsn_fuse = tf.reshape(dsn_fuse, [batch_size, const.image_height, const.image_width, 5]) #without this, will get error: ValueError: Number of in_channels must be known. dsn_fuse = slim.conv2d(dsn_fuse, 1, [1, 1], scope='dsn_fuse') return dsn_fuse, dsn1, dsn2, dsn3, dsn4, dsn5
论文给出的 HED 网络是一个通用的边缘检测网络,按照论文的描述,每个尺度上获得的 image,都须要参与 cost 的计算,这部分的代码以下:
input_queue_for_train = tf.train.string_input_producer([FLAGS.csv_path]) image_tensor, annotation_tensor = input_image_pipeline(dataset_root_dir_string, input_queue_for_train, FLAGS.batch_size) dsn_fuse, dsn1, dsn2, dsn3, dsn4, dsn5 = hed_net(image_tensor, FLAGS.batch_size) cost = class_balanced_sigmoid_cross_entropy(dsn_fuse, annotation_tensor) + \ class_balanced_sigmoid_cross_entropy(dsn1, annotation_tensor) + \ class_balanced_sigmoid_cross_entropy(dsn2, annotation_tensor) + \ class_balanced_sigmoid_cross_entropy(dsn3, annotation_tensor) + \ class_balanced_sigmoid_cross_entropy(dsn4, annotation_tensor) + \ class_balanced_sigmoid_cross_entropy(dsn5, annotation_tensor)
按照这种方式训练出来的网络,检测到的边缘线是有一点粗的,为了获得更细的边缘线,经过屡次试验找到了一种优化方案,代码以下:
input_queue_for_train = tf.train.string_input_producer([FLAGS.csv_path]) image_tensor, annotation_tensor = input_image_pipeline(dataset_root_dir_string, input_queue_for_train, FLAGS.batch_size) dsn_fuse, _, _, _, _, _ = hed_net(image_tensor, FLAGS.batch_size) cost = class_balanced_sigmoid_cross_entropy(dsn_fuse, annotation_tensor)
也就是再也不让每一个尺度上获得的 image 都参与 cost 的计算,只使用融合后获得的最终 image 来进行计算。
两种 cost 函数的效果对好比下图所示,右侧是优化事后的效果:
另外还有一点,按照 HED 论文里的要求,计算 cost 的时候,不能使用常见的方差 cost,而应该使用 cost-sensitive loss function,代码以下:
def class_balanced_sigmoid_cross_entropy(logits, label, name='cross_entropy_loss'): """ The class-balanced cross entropy loss, as in `Holistically-Nested Edge Detection <http://arxiv.org/abs/1504.06375>`_. This is more numerically stable than class_balanced_cross_entropy :param logits: size: the logits. :param label: size: the ground truth in {0,1}, of the same shape as logits. :returns: a scalar. class-balanced cross entropy loss """ y = tf.cast(label, tf.float32) count_neg = tf.reduce_sum(1. - y) # the number of 0 in y count_pos = tf.reduce_sum(y) # the number of 1 in y (less than count_neg) beta = count_neg / (count_neg + count_pos) pos_weight = beta / (1 - beta) cost = tf.nn.weighted_cross_entropy_with_logits(logits, y, pos_weight) cost = tf.reduce_mean(cost * (1 - beta), name=name) return cost
在尝试 FCN 网络的时候,就被这个问题卡住过很长一段时间,按照 FCN 的要求,在使用转置卷积(transposed convolution)/反卷积(deconv)的时候,要把卷积核的值初始化成双线性放大矩阵(bilinear upsampling kernel),而不是经常使用的正态分布随机初始化,同时还要使用很小的学习率,这样才更容易让模型收敛。
HED 的论文中,并无明确的要求也要采用这种方式初始化转置卷积层,可是,在训练过程当中发现,采用这种方式进行初始化,模型才更容易收敛。
这部分的代码以下:
def get_kernel_size(factor): """ Find the kernel size given the desired factor of upsampling. """ return 2 * factor - factor % 2 def upsample_filt(size): """ Make a 2D bilinear kernel suitable for upsampling of the given (h, w) size. """ factor = (size + 1) // 2 if size % 2 == 1: center = factor - 1 else: center = factor - 0.5 og = np.ogrid[:size, :size] return (1 - abs(og[0] - center) / factor) * (1 - abs(og[1] - center) / factor) def bilinear_upsample_weights(factor, number_of_classes): """ Create weights matrix for transposed convolution with bilinear filter initialization. """ filter_size = get_kernel_size(factor) weights = np.zeros((filter_size, filter_size, number_of_classes, number_of_classes), dtype=np.float32) upsample_kernel = upsample_filt(filter_size) for i in xrange(number_of_classes): weights[:, :, i, i] = upsample_kernel return weights
HED 网络不像 VGG 网络那样很容易就进入收敛状态,也不太容易进入指望的理想状态,主要是两方面的缘由:
为了解决这里遇到的问题,采用的办法就是先使用少许样本图片(好比 2000 张)训练网络,在很短的训练时间(好比迭代 1000 次)内,若是 HED 网络不能表现出收敛的趋势,或者不能达到 5 个尺度的 image 所有有效的状态,那就直接放弃这轮的训练结果,从新开启下一轮训练,直到满意为止,而后才使用完整的训练样本集合继续训练网络。
HED 论文里使用的训练数据集,是针对通用的边缘检测目的的,什么形状的边缘都有,好比下面这种:
用这份数据训练出来的模型,在作文档扫描的时候,检测出来的边缘效果并不理想,并且这份训练数据集的样本数量也很小,只有一百多张图片(由于这种图片的人工标注成本过高了),这也会影响模型的质量。
如今的需求里,要检测的是具备必定透视和旋转变换效果的矩形区域,因此能够大胆的猜想,若是准备一批针对性更强的训练样本,应该是能够获得更好的边缘检测效果的。
借助初版技术方案收集回来的真实场景图片,咱们开发了一套简单的标注工具,人工标注了 1200 张图片(标注这 1200 张图片的时间成本也很高),可是这 1200 多张图片仍然有不少问题,好比对于神经网络来讲,1200 个训练样本其实仍是不够的,另外,这些图片覆盖的场景其实也比较少,有些图片的类似度比较高,这样的数据放到神经网络里训练,泛化的效果并很差。
因此,还采用技术手段,合成了80000多张训练样本图片。
如上图所示,一张背景图和一张前景图,能够合成出一对训练样本数据。在合成图片的过程当中,用到了下面这些技术和技巧:
通过不断的调整和优化,最终才训练出一个满意的模型,能够再次经过下面这张图表中的第二列看一下神经网络模型的边缘检测效果:
TensorFlow 官方是支持 iOS 和 Android 的,并且有清晰的文档,照着作就行。可是由于 TensorFlow 是依赖于 protobuf 3 的,因此有可能会遇到一些其余的问题,好比下面这两种,就是咱们在两个不一样的 iOS APP 中遇到的问题和解决办法,能够做为一个参考:
Android 上由于自己是可使用动态库的,因此即使 app 必须使用 protobuf 2 也没有关系,不一样的模块使用 dlopen 的方式加载各自须要的特定版本的库就能够了。
模型一般都是在 PC 端训练的,对于大部分使用者,都是用 Python 编写的代码,获得 ckpt 格式的模型文件。在使用模型文件的时候,一种作法就是用代码从新构建出完整的神经网络,而后加载这个 ckpt 格式的模型文件,若是是在 PC 上使用模型文件,用这个方法其实也是能够接受的,复制粘贴一下 Python 代码就能够从新构建整个神经网络。可是,在手机上只能使用 TensorFlow 提供的 C++ 接口,若是仍是用一样的思路,就须要用 C++ API 从新构建一遍神经网络,这个工做量就有点大了,并且 C++ API 使用起来比 Python API 复杂的多,因此,在 PC 上训练完网络后,还须要把 ckpt 格式的模型文件转换成 pb 格式的模型文件,这个 pb 格式的模型文件,是用 protobuf 序列化获得的二进制文件,里面包含了神经网络的具体结构以及每一个矩阵的数值,使用这个 pb 文件的时候,不须要再用代码构建完整的神经网络结构,只须要反序列化一下就能够了,这样的话,用 C++ API 编写的代码就会简单不少,其实这也是 TensorFlow 推荐的使用方法,在 PC 上使用模型的时候,也应该使用这种 pb 文件(训练过程当中使用 ckpt 文件)。
在手机上加载 pb 模型文件而且运行的时候,遇到过一个诡异的错误,内容以下:
Invalid argument: No OpKernel was registered to support Op 'Mul' with these attrs. Registered devices: [CPU], Registered kernels: device='CPU'; T in [DT_FLOAT] [[Node: hed/mul_1 = Mul[T=DT_INT32](hed/strided_slice_2, hed/mul_1/y)]]
之因此诡异,是由于从字面上看,这个错误的含义是缺乏乘法操做(Mul),可是我用其余的神经网络模型作过对比,乘法操做模块是能够正常工做的。
Google 搜索后发现不少人遇到过相似的状况,可是错误信息又并不相同,后来在 TensorFlow 的 github issues 里终于找到了线索,综合起来解释,是由于 TensorFlow 是基于操做(Operation)来模块化设计和编码的,每个数学计算模块就是一个 Operation,因为各类缘由,好比内存占用大小、GPU 独占操做等等,mobile 版的 TensorFlow,并无包含全部的 Operation,mobile 版的 TensorFlow 支持的 Operation 只是 PC 完整版 TensorFlow 的一个子集,我遇到的这个错误,就是由于使用到的某个 Operation 并不支持 mobile 版。
按照这个线索,在 Python 代码中逐个排查,后来定位到了出问题的代码,修改先后的代码以下:
def deconv(inputs, upsample_factor): input_shape = tf.shape(inputs) # Calculate the ouput size of the upsampled tensor upsampled_shape = tf.pack([input_shape[0], input_shape[1] * upsample_factor, input_shape[2] * upsample_factor, 1]) upsample_filter_np = bilinear_upsample_weights(upsample_factor, 1) upsample_filter_tensor = tf.constant(upsample_filter_np) # Perform the upsampling upsampled_inputs = tf.nn.conv2d_transpose(inputs, upsample_filter_tensor, output_shape=upsampled_shape, strides=[1, upsample_factor, upsample_factor, 1]) return upsampled_inputs def deconv_mobile_version(inputs, upsample_factor, upsampled_shape): upsample_filter_np = bilinear_upsample_weights(upsample_factor, 1) upsample_filter_tensor = tf.constant(upsample_filter_np) # Perform the upsampling upsampled_inputs = tf.nn.conv2d_transpose(inputs, upsample_filter_tensor, output_shape=upsampled_shape, strides=[1, upsample_factor, upsample_factor, 1]) return upsampled_inputs
问题就是由 deconv 函数中的 tf.shape 和 tf.pack 这两个操做引发的,在 PC 版代码中,为了简洁,是基于这两个操做,自动计算出 upsampled_shape,修改事后,则是要求调用者用 hard coding 的方式设置对应的 upsampled_shape。
TensorFlow 是一个很庞大的框架,对于手机来讲,它占用的体积是比较大的,因此须要尽可能的缩减 TensorFlow 库占用的体积。
其实在解决前面遇到的那个 crash 问题的时候,已经指明了一种裁剪的思路,既然 mobile 版的 TensorFlow 原本就是 PC 版的一个子集,那就意味着能够根据具体的需求,让这个子集变得更小,这也就达到了裁剪的目的。具体来讲,就是修改 TensorFlow 源码中的 tensorflow/tensorflow/contrib/makefile/tf_op_files.txt 文件,只保留使用到了的模块。针对 HED 网络,原有的 200 多个模块裁剪到只剩 46 个,裁剪事后的 tf_op_files.txt 文件以下:
tensorflow/core/kernels/xent_op.cc tensorflow/core/kernels/where_op.cc tensorflow/core/kernels/unpack_op.cc tensorflow/core/kernels/transpose_op.cc tensorflow/core/kernels/transpose_functor_cpu.cc tensorflow/core/kernels/tensor_array_ops.cc tensorflow/core/kernels/tensor_array.cc tensorflow/core/kernels/split_op.cc tensorflow/core/kernels/split_v_op.cc tensorflow/core/kernels/split_lib_cpu.cc tensorflow/core/kernels/shape_ops.cc tensorflow/core/kernels/session_ops.cc tensorflow/core/kernels/sendrecv_ops.cc tensorflow/core/kernels/reverse_op.cc tensorflow/core/kernels/reshape_op.cc tensorflow/core/kernels/relu_op.cc tensorflow/core/kernels/pooling_ops_common.cc tensorflow/core/kernels/pack_op.cc tensorflow/core/kernels/ops_util.cc tensorflow/core/kernels/no_op.cc tensorflow/core/kernels/maxpooling_op.cc tensorflow/core/kernels/matmul_op.cc tensorflow/core/kernels/immutable_constant_op.cc tensorflow/core/kernels/identity_op.cc tensorflow/core/kernels/gather_op.cc tensorflow/core/kernels/gather_functor.cc tensorflow/core/kernels/fill_functor.cc tensorflow/core/kernels/dense_update_ops.cc tensorflow/core/kernels/deep_conv2d.cc tensorflow/core/kernels/xsmm_conv2d.cc tensorflow/core/kernels/conv_ops_using_gemm.cc tensorflow/core/kernels/conv_ops_fused.cc tensorflow/core/kernels/conv_ops.cc tensorflow/core/kernels/conv_grad_filter_ops.cc tensorflow/core/kernels/conv_grad_input_ops.cc tensorflow/core/kernels/conv_grad_ops.cc tensorflow/core/kernels/constant_op.cc tensorflow/core/kernels/concat_op.cc tensorflow/core/kernels/concat_lib_cpu.cc tensorflow/core/kernels/bias_op.cc tensorflow/core/ops/sendrecv_ops.cc tensorflow/core/ops/no_op.cc tensorflow/core/ops/nn_ops.cc tensorflow/core/ops/nn_grad.cc tensorflow/core/ops/array_ops.cc tensorflow/core/ops/array_grad.cc
须要强调的一点是,这种操做思路,是针对不一样的神经网络结构有不一样的裁剪方式,原则就是用到什么模块就保留什么模块。固然,由于有些模块之间还存在隐含的依赖关系,因此裁剪的时候也是要反复尝试屡次才能成功的。
除此以外,还有下面这些通用手段也能够实现裁剪的目的:
借助全部这些裁剪手段,最终咱们的 ipa 安装包的大小只增长了 3M。若是不作手动裁剪这一步,那 ipa 的增量,则是 30M 左右。
按照 HED 论文给出的参考信息,获得的模型文件的大小是 56M,对于手机来讲也是比较大的,并且模型越大也意味着计算量越大,因此须要考虑可否把 HED 网络也裁剪一下。
HED 网络是用 VGG16 做为基础网络结构,而 VGG 又是一个获得普遍验证的基础网络结构,所以修改 HED 的总体结构确定不是一个明智的选择,至少不是首选的方案。
考虑到如今的需求,只是检测矩形区域的边缘,而并非检测通用场景下的广义的边缘,能够认为前者的复杂度比后者更低,因此一种可行的思路,就是保留 HED 的总体结构,修改 VGG 每一组卷积层里面的卷积核的数量,让 HED 网络变的更『瘦』。
按照这种思路,通过屡次调整和尝试,最终获得了一组合适的卷积核的数量参数,对应的模型文件只有 4.2M,在 iPhone 7P 上,处理每帧图片的时间消耗是 0.1 秒左右,知足实时性的要求。
神经网络的裁剪,目前在学术界也是一个很热门的领域,有好几种不一样的理论来实现不一样目的的裁剪,可是,也并非说每一种网络结构都有裁剪的空间,一般来讲,应该结合实际状况,使用合适的技术手段,选择一个合适大小的模型文件。
TensorFlow 的 API 是很灵活的,也比较底层,在学习过程当中发现,每一个人写出来的代码,风格差别很大,并且不少工程师又采用了各类各样的技巧来简化代码,可是这其实反而在无形中又增长了代码的阅读难度,也不利于代码的复用。
第三方社区和 TensorFlow 官方,都意识到了这个问题,因此更好的作法是,使用封装度更高但又保持灵活性的 API 来进行开发。本文中的代码,就是使用 TensorFlow-Slim 编写的。
虽然用神经网络技术,已经获得了一个比 canny 算法更好的边缘检测效果,可是,神经网络也并非万能的,干扰是仍然存在的,因此,第二个步骤中的数学模型算法,仍然是须要的,只不过由于第一个步骤中的边缘检测有了大幅度改善,因此第二个步骤中的算法,获得了适当的简化,并且算法总体的适应性也更强了。
这部分的算法以下图所示:
按照编号顺序,几个关键步骤作了下面这些事情:
对于上面这个例子,初版技术方案中检测出来的边缘线以下图所示:
有兴趣的读者也能够考虑一下,在这种边缘图中,如何设计算法才能找出咱们指望的那个矩形。
Hacker's guide to Neural Networks
神经网络浅讲:从神经元到深度学习
分类与回归区别是什么?
神经网络架构演进史:全面回顾从LeNet5到ENet十余种架构
数据的游戏:冰与火
为何“高大上”的算法工程师变成了数据民工?
Facebook人工智能负责人Yann LeCun谈深度学习的局限性
The best explanation of Convolutional Neural Networks on the Internet!
从入门到精通:卷积神经网络初学者指南
Transposed Convolution, Fractionally Strided Convolution or Deconvolution
A technical report on convolution arithmetic in the context of deep learning
Visualizing what ConvNets learn
Visualizing Features from a Convolutional Neural Network
Neural networks: which cost function to use?
difference between tensorflow tf.nn.softmax and tf.nn.softmax_cross_entropy_with_logits
Why You Should Use Cross-Entropy Error Instead Of Classification Error Or Mean Squared Error For Neural Network Classifier Training
Tensorflow 3 Ways
TensorFlow-Slim
TensorFlow-Slim image classification library
Holistically-Nested Edge Detection
深度卷积神经网络在目标检测中的进展
全卷积网络:从图像级理解到像素级理解
图像语义分割之FCN和CRF
Image Classification and Segmentation with Tensorflow and TF-Slim
Upsampling and Image Segmentation with Tensorflow and TF-Slim
Image Segmentation with Tensorflow using CNNs and Conditional Random Fields
How to Build a Kick-Ass Mobile Document Scanner in Just 5 Minutes
MAKE DOCUMENT SCANNER USING PYTHON AND OPENCV
Fast and Accurate Document Detection for Scanning
更多精彩内容欢迎关注腾讯 Bugly的微信公众帐号:
腾讯 Bugly是一款专为移动开发者打造的质量监控工具,帮助开发者快速,便捷的定位线上应用崩溃的状况以及解决方案。智能合并功能帮助开发同窗把天天上报的数千条 Crash 根据根因合并分类,每日日报会列出影响用户数最多的崩溃,精准定位功能帮助开发同窗定位到出问题的代码行,实时上报能够在发布后快速的了解应用的质量状况,适配最新的 iOS, Android 官方操做系统,鹅厂的工程师都在使用,快来加入咱们吧!