做者:馮牮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 官方操做系統,鵝廠的工程師都在使用,快來加入咱們吧!