日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人工智能 > pytorch >内容正文

pytorch

使用深度学习解决拍照文档复杂背景二值化问题

發布時間:2025/3/21 pytorch 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 使用深度学习解决拍照文档复杂背景二值化问题 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

1.在手持拍照設備對文檔進行拍照時,很容易出現光線不均、陰影、過暗等,或者有些舊的文檔,古籍文檔都有蟲洞、透背、字跡不清現象,為了方便閱讀、打印文檔,或者OCR識別,這些干擾都對處理結果有很多不良的影響。
2.在文檔處理過程,往往分這幾步,圖像預處理、文檔圖像二值化、版面分析、文本檢測與識別等環節,在文檔才二值這塊,有很多傳統的算法可以使用,比如大津法,自適應二值化等,但在使用的過程,這些傳統的算法只對針對某些特定的干擾做深度調參,并不能達到對所有文檔都有高魯棒性。

一.傳統方法

1.傳統數字圖像處理里面,對圖像二值化有好多用可用辦法,我自己試過幾種方法,從最常用的大津法、自適應二值化,到比較偏門的積分二值化。下面來對比下這幾種方法的效果。

效果圖像第一是灰度圖像,第二張是自適應二值化,第三張是大津法,第四張是積分二值化。

第一種場景,手寫文檔,光線不均,有少些陰影:
原圖:

二值化效果圖像:

第二種場景,帶大面積陰影的印刷文檔,而且陰影比較明顯:
原圖:

二值圖像效果圖:

第三種場景,有很重透背的文檔:
原圖:

二值圖像效果圖:

第四種場景,紙張帶有底色的古籍手抄文檔:
原圖:

二值圖像效果圖:

2.從以上效果來看,當使用場景有干擾或者光線有變化的情況下,傳統的圖像圖像處理并不能完美的解決文檔圖像二值化的問題,表現稍微好一些積分二值化(效果圖像第四格)也不能勝任大部分環境。但有些使用場景相對穩定的情況下,可以選擇積分二值化這個方法。下面是積分二值化的代碼,是基于OpenCV C++寫的。

/// <summary> /// 積分二值化 /// </summary> /// <param name="inputMat">輸入圖像</param> /// <param name="thre">閾值(1.0)</param> /// <param name="outputMat">輸出圖像</param> void thresholdIntegral(cv::Mat& inputMat, double thre, cv::Mat& outputMat) {// accept only char type matricesCV_Assert(!inputMat.empty());CV_Assert(inputMat.depth() == CV_8U);CV_Assert(inputMat.channels() == 1);outputMat = cv::Mat(inputMat.size(), CV_8UC1, 1);// rows -> height -> yint nRows = inputMat.rows;// cols -> width -> xint nCols = inputMat.cols;// create the integral imagecv::Mat sumMat;cv::integral(inputMat, sumMat);CV_Assert(sumMat.depth() == CV_32S);CV_Assert(sizeof(int) == 4);int S = MAX(nRows, nCols) / 8;double T = 0.15;// perform thresholdingint s2 = S / 2;int x1, y1, x2, y2, count, sum;// CV_Assert(sizeof(int) == 4);int* p_y1, * p_y2;uchar* p_inputMat, * p_outputMat;for (int i = 0; i < nRows; ++i){y1 = i - s2;y2 = i + s2;if (y1 < 0){y1 = 0;}if (y2 >= nRows){y2 = nRows - 1;}p_y1 = sumMat.ptr<int>(y1);p_y2 = sumMat.ptr<int>(y2);p_inputMat = inputMat.ptr<uchar>(i);p_outputMat = outputMat.ptr<uchar>(i);for (int j = 0; j < nCols; ++j){// set the SxS regionx1 = j - s2;x2 = j + s2;if (x1 < 0){x1 = 0;}if (x2 >= nCols){x2 = nCols - 1;}count = (x2 - x1) * (y2 - y1);// I(x,y)=s(x2,y2)-s(x1,y2)-s(x2,y1)+s(x1,x1)sum = p_y2[x2] - p_y1[x2] - p_y2[x1] + p_y1[x1];if ((int)(p_inputMat[j] * count) < (int)(sum * (1.0 - T) * thre))p_outputMat[j] = 0;elsep_outputMat[j] = 255;}} }

3.當使用環境不確定或者使用環境比較復雜的時候,傳統方法再什么調參也不能完美解決,這個時候只能考慮使用深度學習了。

二.基于U-net的圖像二值化

1.Unet 網絡
U-Net一開始就是針對生物醫學圖片的分割用的,一直到現在許多對醫學圖像的分割網絡中,很大一部分會采取U-Net作為網絡的主干。
算法部分我這里參考的U-Net關于視網膜血管分割這個項目,github地址:https://github.com/orobix/retina-unet 。我們可以看看它對視網膜血管分割。

2.深度學習框架用的Pytorch,參考了這個項目:https://github.com/milesial/Pytorch-UNet添加鏈接描述 。
3.但U-Net只能訓練尺寸為512的圖像,但對于拍攝的文檔,如果把尺寸都壓到512來做標簽和訓練,肯定會丟失好多細節上的東西,我這里把網絡按ENet的結構做了微調。

三.模型推理

1.我的測試環境是win10,vs2019,OpenCV 4.5,模型推理這里為了方便,就直接用OpenCV的dnn。
2.代碼:

DirtyDocUnet類:

#pragma once #include <iostream> #include <string> #include <opencv2/opencv.hpp> #include <opencv2/dnn/dnn.hpp>class DirtyDocUnet { public:DirtyDocUnet(std::string _model_path);void dnnInference(cv::Mat &cv_src, cv::Mat &cv_dst);void docBin(const cv::Mat& cv_src, cv::Mat& cv_dst); private:std::string model_path;cv::dnn::Net doc_net;int target_w = 1560;int target_h = 1560; }; #include "DirtyDocUnet.h"DirtyDocUnet::DirtyDocUnet(std::string _model_path) {model_path = _model_path;doc_net = cv::dnn::readNet(model_path); }void DirtyDocUnet::dnnInference(cv::Mat &cv_src, cv::Mat &cv_dst) {cv::Size reso(this->target_w,this->target_h);cv::Mat cv_gray;cv::cvtColor(cv_src, cv_gray, cv::COLOR_BGR2GRAY);cv::Mat bold = cv::dnn::blobFromImage(cv_gray, 1.0 / 255, reso, cv::Scalar(0, 0, 0), false, false);doc_net.setInput(bold);cv::Mat cv_out = doc_net.forward();cv::Mat cv_seg = cv::Mat::zeros(cv_out.size[2], cv_out.size[3], CV_8UC1);for (int i = 0; i < cv_out.size[2] * cv_out.size[3]; i++){cv_seg.data[i] = cv_out.ptr<float>(0, 0)[i] * 255;} cv::resize(cv_seg, cv_dst, cv_src.size()); }/// <summary> /// 二值圖像的邊緣光滑處理 /// </summary> /// <param name="src">輸入圖像</param> /// <param name="dst">輸出圖像</param> /// <param name="uthreshold">寬度閾值</param> /// <param name="vthreshold">高度閾值</param> /// <param name="type">突出部的顏色,0表示黑色,1代表白色</param> void deleteZigzag(cv::Mat& src, cv::Mat& dst, int uthreshold, int vthreshold, int type) {//int threshold;src.copyTo(dst);int height = dst.rows;int width = dst.cols;int k; //用于循環計數傳遞到外部for (int i = 0; i < height - 1; i++){uchar* p = dst.ptr<uchar>(i);for (int j = 0; j < width - 1; j++){if (type == 0){//行消除if (p[j] == 255 && p[j + 1] == 0){if (j + uthreshold >= width){for (int k = j + 1; k < width; k++){p[k] = 255;}}else{for (k = j + 2; k <= j + uthreshold; k++){if (p[k] == 255){break;}}if (p[k] == 255){for (int h = j + 1; h < k; h++){p[h] = 255;}}}}//列消除if (p[j] == 255 && p[j + width] == 0){if (i + vthreshold >= height){for (k = j + width; k < j + (height - i) * width; k += width){p[k] = 255;}}else{for (k = j + 2 * width; k <= j + vthreshold * width; k += width){if (p[k] == 255) break;}if (p[k] == 255){for (int h = j + width; h < k; h += width)p[h] = 255;}}}}else //type = 1{//行消除if (p[j] == 0 && p[j + 1] == 255){if (j + uthreshold >= width){for (int k = j + 1; k < width; k++)p[k] = 0;}else{for (k = j + 2; k <= j + uthreshold; k++){if (p[k] == 0) break;}if (p[k] == 0){for (int h = j + 1; h < k; h++)p[h] = 0;}}}//列消除if (p[j] == 0 && p[j + width] == 255){if (i + vthreshold >= height){for (k = j + width; k < j + (height - i) * width; k += width)p[k] = 0;}else{for (k = j + 2 * width; k <= j + vthreshold * width; k += width){if (p[k] == 0) break;}if (p[k] == 0){for (int h = j + width; h < k; h += width)p[h] = 0;}}}}}} }void DirtyDocUnet::docBin(const cv::Mat& cv_src, cv::Mat& cv_dst) {if (cv_src.empty()){return;}std::vector<cv::Mat> cv_pieces;cv_pieces.push_back(cv_src(cv::Rect(0, 0, cv_src.cols, cv_src.rows / 2)));cv_pieces.push_back(cv_src(cv::Rect(0, cv_src.rows / 2, cv_src.cols, cv_src.rows / 2)));cv::Mat cv_pars;for (auto v : cv_pieces){cv::Mat cv_temp;dnnInference(v, cv_temp);cv_pars.push_back(cv_temp);}cv::Mat cv_resize;cv::resize(~cv_pars, cv_resize, cv::Size(4096, 4096), cv::INTER_CUBIC);cv::Mat cv_zig;deleteZigzag(cv_resize, cv_zig, 5, 5, 0);cv::Mat cv_bin;cv::resize(~cv_zig, cv_dst, cv::Size(cv_src.cols, cv_src.rows), cv::INTER_LINEAR); } #include "DirtyDocUnet.h"DirtyDocUnet::DirtyDocUnet(std::string _model_path) {model_path = _model_path;doc_net = cv::dnn::readNet(model_path); }void DirtyDocUnet::dnnInference(cv::Mat &cv_src, cv::Mat &cv_dst) {cv::Size reso(this->target_w,this->target_h);cv::Mat cv_gray;cv::cvtColor(cv_src, cv_gray, cv::COLOR_BGR2GRAY);cv::Mat bold = cv::dnn::blobFromImage(cv_gray, 1.0 / 255, reso, cv::Scalar(0, 0, 0), false, false);doc_net.setInput(bold);cv::Mat cv_out = doc_net.forward();cv::Mat cv_seg = cv::Mat::zeros(cv_out.size[2], cv_out.size[3], CV_8UC1);for (int i = 0; i < cv_out.size[2] * cv_out.size[3]; i++){cv_seg.data[i] = cv_out.ptr<float>(0, 0)[i] * 255;} cv::resize(cv_seg, cv_dst, cv_src.size()); }/// <summary> /// 二值圖像的邊緣光滑處理 /// </summary> /// <param name="src">輸入圖像</param> /// <param name="dst">輸出圖像</param> /// <param name="uthreshold">寬度閾值</param> /// <param name="vthreshold">高度閾值</param> /// <param name="type">突出部的顏色,0表示黑色,1代表白色</param> void deleteZigzag(cv::Mat& src, cv::Mat& dst, int uthreshold, int vthreshold, int type) {//int threshold;src.copyTo(dst);int height = dst.rows;int width = dst.cols;int k; //用于循環計數傳遞到外部for (int i = 0; i < height - 1; i++){uchar* p = dst.ptr<uchar>(i);for (int j = 0; j < width - 1; j++){if (type == 0){//行消除if (p[j] == 255 && p[j + 1] == 0){if (j + uthreshold >= width){for (int k = j + 1; k < width; k++){p[k] = 255;}}else{for (k = j + 2; k <= j + uthreshold; k++){if (p[k] == 255){break;}}if (p[k] == 255){for (int h = j + 1; h < k; h++){p[h] = 255;}}}}//列消除if (p[j] == 255 && p[j + width] == 0){if (i + vthreshold >= height){for (k = j + width; k < j + (height - i) * width; k += width){p[k] = 255;}}else{for (k = j + 2 * width; k <= j + vthreshold * width; k += width){if (p[k] == 255) break;}if (p[k] == 255){for (int h = j + width; h < k; h += width)p[h] = 255;}}}}else //type = 1{//行消除if (p[j] == 0 && p[j + 1] == 255){if (j + uthreshold >= width){for (int k = j + 1; k < width; k++)p[k] = 0;}else{for (k = j + 2; k <= j + uthreshold; k++){if (p[k] == 0) break;}if (p[k] == 0){for (int h = j + 1; h < k; h++)p[h] = 0;}}}//列消除if (p[j] == 0 && p[j + width] == 255){if (i + vthreshold >= height){for (k = j + width; k < j + (height - i) * width; k += width)p[k] = 0;}else{for (k = j + 2 * width; k <= j + vthreshold * width; k += width){if (p[k] == 0) break;}if (p[k] == 0){for (int h = j + width; h < k; h += width)p[h] = 0;}}}}}} }void DirtyDocUnet::docBin(const cv::Mat& cv_src, cv::Mat& cv_dst) {if (cv_src.empty()){return;}std::vector<cv::Mat> cv_pieces;cv_pieces.push_back(cv_src(cv::Rect(0, 0, cv_src.cols, cv_src.rows / 2)));cv_pieces.push_back(cv_src(cv::Rect(0, cv_src.rows / 2, cv_src.cols, cv_src.rows / 2)));cv::Mat cv_pars;for (auto v : cv_pieces){cv::Mat cv_temp;dnnInference(v, cv_temp);cv_pars.push_back(cv_temp);}cv::Mat cv_resize;cv::resize(~cv_pars, cv_resize, cv::Size(4096, 4096), cv::INTER_CUBIC);cv::Mat cv_zig;deleteZigzag(cv_resize, cv_zig, 5, 5, 0);cv::Mat cv_bin;cv::resize(~cv_zig, cv_dst, cv::Size(cv_src.cols, cv_src.rows), cv::INTER_LINEAR); }

調用類
main.cpp

#include <iostream> #include "DirtyDocUnet.h"void thresholdIntegral(cv::Mat& inputMat, double thre, cv::Mat& outputMat); void mergeImages(const cv::Mat& cv_src1, const cv::Mat& cv_src2, cv::Mat& cv_dst);void imshow(std::string name, const cv::Mat& cv_src) {cv::namedWindow(name, 0);int max_rows = 800;int max_cols = 800;if (cv_src.rows >= cv_src.cols && cv_src.rows > max_rows){cv::resizeWindow(name, cv::Size(cv_src.cols * max_rows / cv_src.rows, max_rows));}else if (cv_src.cols >= cv_src.rows && cv_src.cols > max_cols){cv::resizeWindow(name, cv::Size(max_cols, cv_src.rows * max_cols / cv_src.cols));}cv::imshow(name, cv_src); }int main(void) {std::string path = "images";std::vector<std::string> filenames;cv::glob(path, filenames, false);std::string model_path = "models/unetv2.onnx";DirtyDocUnet doc_bin(model_path);int i = 0;for (auto v : filenames){cv::Mat cv_src = cv::imread(v);cv::Mat cv_bin, cv_otsu, cv_gray, cv_integral;cv::cvtColor(cv_src, cv_gray, cv::COLOR_BGR2GRAY);cv::threshold(cv_gray, cv_bin, 127, 255, cv::THRESH_BINARY);cv::threshold(cv_gray, cv_otsu, 0, 255, cv::THRESH_OTSU);thresholdIntegral(cv_gray, 1.0, cv_integral);cv::Mat cv_unet;doc_bin.docBin(cv_src, cv_unet);cv_bin.push_back(cv_otsu);cv_integral.push_back(cv_unet);cv::Mat cv_all;mergeImages(cv_bin, cv_integral,cv_all);cv::imwrite(v, cv_all);} }/// <summary> /// 積分二值化 /// </summary> /// <param name="inputMat">輸入圖像</param> /// <param name="thre">閾值(1.0)</param> /// <param name="outputMat">輸出圖像</param> void thresholdIntegral(cv::Mat& inputMat, double thre, cv::Mat& outputMat) {// accept only char type matricesCV_Assert(!inputMat.empty());CV_Assert(inputMat.depth() == CV_8U);CV_Assert(inputMat.channels() == 1);outputMat = cv::Mat(inputMat.size(), CV_8UC1, 1);// rows -> height -> yint nRows = inputMat.rows;// cols -> width -> xint nCols = inputMat.cols;// create the integral imagecv::Mat sumMat;cv::integral(inputMat, sumMat);CV_Assert(sumMat.depth() == CV_32S);CV_Assert(sizeof(int) == 4);int S = MAX(nRows, nCols) / 8;double T = 0.15;// perform thresholdingint s2 = S / 2;int x1, y1, x2, y2, count, sum;// CV_Assert(sizeof(int) == 4);int* p_y1, * p_y2;uchar* p_inputMat, * p_outputMat;for (int i = 0; i < nRows; ++i){y1 = i - s2;y2 = i + s2;if (y1 < 0){y1 = 0;}if (y2 >= nRows){y2 = nRows - 1;}p_y1 = sumMat.ptr<int>(y1);p_y2 = sumMat.ptr<int>(y2);p_inputMat = inputMat.ptr<uchar>(i);p_outputMat = outputMat.ptr<uchar>(i);for (int j = 0; j < nCols; ++j){// set the SxS regionx1 = j - s2;x2 = j + s2;if (x1 < 0){x1 = 0;}if (x2 >= nCols){x2 = nCols - 1;}count = (x2 - x1) * (y2 - y1);// I(x,y)=s(x2,y2)-s(x1,y2)-s(x2,y1)+s(x1,x1)sum = p_y2[x2] - p_y1[x2] - p_y2[x1] + p_y1[x1];if ((int)(p_inputMat[j] * count) < (int)(sum * (1.0 - T) * thre))p_outputMat[j] = 0;elsep_outputMat[j] = 255;}} }void mergeImages(const cv::Mat& cv_src1, const cv::Mat& cv_src2, cv::Mat& cv_dst) {CV_Assert(!(cv_src1.rows != cv_src2.rows || cv_src1.cols != cv_src2.cols));CV_Assert(!(cv_src1.empty() || cv_src2.empty()));cv_dst.create(cv_src1.rows, cv_src1.cols * 2, cv_src1.type());cv::Mat r1 = cv_dst(cv::Rect(0, 0, cv_src1.cols, cv_src1.rows));cv_src1.copyTo(r1);cv::Mat r2 = cv_dst(cv::Rect(cv_src1.cols, 0, cv_src1.cols, cv_src1.rows));cv_src2.copyTo(r2); }

3.對比下處理效果
原圖:

二值圖像,第一張是自適應二值化,第二張是積分二值化,第三張是大津法二值化,第四張是UNet二值化的效果:

原圖:

二值圖像,第一張是自適應二值化,第二張是積分二值化,第三張是大津法二值化,第四張是UNet二值化的效果:

原圖:

二值圖像,第一張是自適應二值化,第二張是積分二值化,第三張是大津法二值化,第四張是UNet二值化的效果:

原圖:

二值圖像,第一張是自適應二值化,第二張是積分二值化,第三張是大津法二值化,第四張是UNet二值化的效果:

原圖:

二值圖像,第一張是自適應二值化,第二張是積分二值化,第三張是大津法二值化,第四張是UNet二值化的效果:

4.從整體的效果上看, 使用深度學習方法,最終的效果不管在什么樣的環境在,都能得到一個不錯的。其實這個效果還有可提升的空間,我當前用的訓練集大概是2000張左右的樣本,如果還能增加更多環境下的樣本,那模型泛化會更好。
5.這個效果在一些手機掃描類APP里面也有類似的功能,一般叫省墨模式,或者黑白掃描,我們在安卓和iOS上都移植了這個算法,下面是我們iOS APP里面的效果,對移動端掃描APP感興趣的可以去試試《掃描家》這個APP。

6.可執行文件和源碼都上傳到CSDN,可執行文件把圖像放到images,執行exe文件就在當前目錄下保存幾種效果的對比,地址:https://download.csdn.net/download/matt45m/50653819 ,源碼:https://download.csdn.net/download/matt45m/50654793

總結

以上是生活随笔為你收集整理的使用深度学习解决拍照文档复杂背景二值化问题的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 欧美综合在线视频 | a级黄色一级片 | 日本少妇裸体做爰 | 无码少妇精品一区二区免费动态 | 黄色一级大片在线免费看国产一 | 91精品免费在线 | 黄色网址在线免费播放 | 亚州av| 国产精品久久久久久久久久直播 | 麻豆成人av| 一本大道久久a久久综合婷婷 | 中文综合网 | 日韩成人av网址 | 亚洲第七页 | 天天爽天天爽夜夜爽毛片 | 亚洲自拍偷拍一区二区 | 红桃视频国产 | 国产无遮挡又黄又爽 | 人人澡人人澡人人 | 另类中文字幕 | 成人免费自拍视频 | 澳门色网 | 国产精品-色哟哟 | 日本成人免费视频 | 一区二区三区高清 | av在观看| 在线不卡二区 | 看av网址 | 欧美性bbw | 精品黑人一区二区三区久久 | 操屁股视频| 精品久久久免费 | 亚洲永久在线 | 夜夜小视频 | 免费一级片视频 | 麻豆精品在线视频 | 免费av日韩 | 漂亮人妻被中出中文字幕 | 女人又爽又黄免费女仆 | 一级女性全黄久久生活片免费 | 新版天堂资源中文8在线 | 国产新婚疯狂做爰视频 | 亚洲天天看 | 亚洲国产精品久久精品怡红院 | 国产乱人乱偷精品视频a人人澡 | 男人资源站 | 一区二区三区中文字幕在线观看 | 亚洲激情五月婷婷 | 国产视频手机在线 | 理论片亚洲| 久草一本| 久久久国产成人一区二区三区 | 亚洲免费观看在线 | 欧美午夜久久久 | 一区二区三区小说 | 色二区 | 久久91亚洲 | 五月婷婷六月合 | 秋霞影院午夜老牛影院 | 久久久久久日产精品 | 无码日韩人妻精品久久蜜桃 | 国语对白| 处破女av一区二区 | 欧美精品大片 | 亚洲va在线 | 日韩一级理论片 | 国产激情一区二区三区四区 | 免费av网址大全 | 爆乳2把你榨干哦ova在线观看 | 久草这里只有精品 | 插综合| 日韩二三区 | 激情综合色 | 亚洲综合网在线观看 | 2024男人天堂 | 日本成人一区二区三区 | 色妞综合网 | 五月天婷婷激情视频 | 少妇系列在线观看 | 日本黄色一级网站 | 大地资源中文在线观看免费版 | 狠狠干一区二区 | 日韩国产精品久久 | 精品国产免费人成在线观看 | 国产精品天天av精麻传媒 | 一极黄色大片 | 肮脏的交易在线观看 | www嫩草| 免费在线观看一区二区 | 日本一区二区在线免费观看 | 人妻中文字幕一区 | 欧美视频免费看欧美视频 | 国产一级在线观看视频 | 国产主播一区二区 | 亚洲最大的网站 | 久操视频免费 | 欧美性生活一区 | 亚洲精选国产 | 国产精品无码天天爽视频 |