需求:老婆工作需要,要做一个粘贴或上传图片后,能自动识别图片中的文字和数字以及英文的功能。
思考及调研:查阅了一些相关资料,发现很多大的厂商(百度、阿里、腾讯等)已有相关的开放接口供应,可满足不同客户需要。主要就是OCR功能,不了解的可以自行度娘。
话不多说,开始编码。(因该功能我是集成到了自己的一个自有项目中,不便贴完整,所以本文贴的代码都为主体代码,是完全可以实现的。)
一、前端页面
使用的是百度的webuploader,可直接截图粘贴,可拖拽式上传
index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <title>文字识别</title> <link rel="shortcut icon" href="favicon.ico"/> <link href="/css/bootstrap.min.css?v=3.3.6" rel="stylesheet"/> <link href="/css/font-awesome.css?v=4.4.0" rel="stylesheet"/> <link href="/css/animate.css" rel="stylesheet"/> <link href="/css/style.css?v=4.1.0" rel="stylesheet"/> <link rel="stylesheet" type="text/css" href="/css/plugins/webuploader/webuploader.css"/> <link rel="stylesheet" type="text/css" href="/css/demo/webuploader-demo.css"/> </head> <body class="gray-bg"> <div class="wrapper wrapper-content animated fadeInRight"> <div class="row"> <div class="ibox"> <div class="ibox-title"> <h5>图片上传</h5> </div> <div class="ibox-content"> <div class="row row-lg"> <div class="alert alert-success" id="ocrResult"> <p> 您可以尝试文件拖拽,使用QQ截屏工具,然后激活窗口后粘贴,或者点击添加图片按钮,来上传图片。</p> </div> <div id="uploader" class="wu-example"> <div class="queueList"> <div id="dndArea" class="placeholder"> <div id="filePicker"></div> <p>将照片拖到这里,一次只可上传一张。</p> </div> </div> <div class="statusBar" style="display:none;"> <div class="progress"> <span class="text">0%</span> <span class="percentage"></span> </div><div class="info"></div> <div class="btns"> <div id="filePicker2"></div><div class="uploadBtn">开始上传</div> </div> </div> </div> </div> </div> </div> </div> <div class="row"> <div class="ibox"> <div class="ibox-title"> <h5>转换结果</h5> </div> <div class="ibox-content"> <div class="row row-lg"> <div class="col-sm-12"> <div class="alert alert-info"> <p id="orcResult">...</p> </div> </div> </div> </div> </div> </div> </div> <!-- 全局js --> <script src="/js/jquery.min.js?v=2.1.4"></script> <script src="/js/bootstrap.min.js?v=3.3.6"></script> <!-- 自定义js --> <script src="/js/content.js?v=1.0.0"></script> <script type="text/javascript"> // 添加全局站点信息 var BASE_URL = '/js/plugins/webuploader'; </script> <script src="/js/plugins/webuploader/webuploader.min.js"></script> <script src="/js/webuploader/webuploader-demo.js"></script> </body> </html>
webuploader-demo.js
jQuery(function() { var $ = jQuery, // just in case. Make sure it's not an other libaray. $wrap = $('#uploader'), // 图片容器 $queue = $('<ul class="filelist"></ul>') .appendTo( $wrap.find('.queueList') ), // 状态栏,包括进度和控制按钮 $statusBar = $wrap.find('.statusBar'), // 文件总体选择信息。 $info = $statusBar.find('.info'), // 上传按钮 $upload = $wrap.find('.uploadBtn'), // 没选择文件之前的内容。 $placeHolder = $wrap.find('.placeholder'), // 总体进度条 $progress = $statusBar.find('.progress').hide(), // 添加的文件数量 fileCount = 0, // 添加的文件总大小 fileSize = 0, // 优化retina, 在retina下这个值是2 ratio = window.devicePixelRatio || 1, // 缩略图大小 thumbnailWidth = 110 * ratio, thumbnailHeight = 110 * ratio, // 可能有pedding, ready, uploading, confirm, done. state = 'pedding', // 所有文件的进度信息,key为file id percentages = {}, supportTransition = (function(){ var s = document.createElement('p').style, r = 'transition' in s || 'WebkitTransition' in s || 'MozTransition' in s || 'msTransition' in s || 'OTransition' in s; s = null; return r; })(), // WebUploader实例 uploader; if ( !WebUploader.Uploader.support() ) { alert( 'Web Uploader 不支持您的浏览器!如果你使用的是IE浏览器,请尝试升级 flash 播放器'); throw new Error( 'WebUploader does not support the browser you are using.' ); } // 实例化 uploader = WebUploader.create({ pick: { id: '#filePicker', label: '点击选择图片' }, dnd: '#uploader .queueList', paste: document.body, accept: { title: 'Images', extensions: 'jpg,jpeg,bmp,png,JPG,JPEG,BMP,PNG', mimeTypes: 'image/*' }, // swf文件路径 swf: BASE_URL + '/Uploader.swf', disableGlobalDnd: true, chunked: false, server: '/ocr/upload', fileNumLimit: 1, //文件数 fileSizeLimit: 5 * 1024 * 1024, // 50 M fileSingleSizeLimit: 3 * 1024 * 1024, // 3 M }); // 添加“添加文件”的按钮, /*uploader.addButton({ id: '#filePicker2', label: '继续添加' });*/ // 当有文件添加进来时执行,负责view的创建 function addFile( file ) { var $li = $( '<li id="' + file.id + '">' + '<p class="title">' + file.name + '</p>' + '<p class="imgWrap"></p>'+ '<p class="progress"><span></span></p>' + '</li>' ), $btns = $('<div class="file-panel">' + '<span class="cancel">删除</span>' + '<span class="rotateRight">向右旋转</span>' + '<span class="rotateLeft">向左旋转</span></div>').appendTo( $li ), $prgress = $li.find('p.progress span'), $wrap = $li.find( 'p.imgWrap' ), $info = $('<p class="error"></p>'), showError = function( code ) { switch( code ) { case 'exceed_size': text = '文件大小超出'; break; case 'interrupt': text = '上传暂停'; break; default: text = '上传失败,请重试'; break; } $info.text( text ).appendTo( $li ); }; if ( file.getStatus() === 'invalid' ) { showError( file.statusText ); } else { // @todo lazyload $wrap.text( '预览中' ); uploader.makeThumb( file, function( error, src ) { if ( error ) { $wrap.text( '不能预览' ); return; } var img = $('<img src="'+src+'">'); $wrap.empty().append( img ); }, thumbnailWidth, thumbnailHeight ); percentages[ file.id ] = [ file.size, 0 ]; file.rotation = 0; } file.on('statuschange', function( cur, prev ) { if ( prev === 'progress' ) { $prgress.hide().width(0); } else if ( prev === 'queued' ) { $li.off( 'mouseenter mouseleave' ); $btns.remove(); } // 成功 if ( cur === 'error' || cur === 'invalid' ) { console.log( file.statusText ); showError( file.statusText ); percentages[ file.id ][ 1 ] = 1; } else if ( cur === 'interrupt' ) { showError( 'interrupt' ); } else if ( cur === 'queued' ) { percentages[ file.id ][ 1 ] = 0; } else if ( cur === 'progress' ) { $info.remove(); $prgress.css('display', 'block'); } else if ( cur === 'complete' ) { $li.append( '<span class="success"></span>' ); } $li.removeClass( 'state-' + prev ).addClass( 'state-' + cur ); }); $li.on( 'mouseenter', function() { $btns.stop().animate({height: 30}); }); $li.on( 'mouseleave', function() { $btns.stop().animate({height: 0}); }); $btns.on( 'click', 'span', function() { var index = $(this).index(), deg; switch ( index ) { case 0: uploader.removeFile( file ); return; case 1: file.rotation += 90; break; case 2: file.rotation -= 90; break; } if ( supportTransition ) { deg = 'rotate(' + file.rotation + 'deg)'; $wrap.css({ '-webkit-transform': deg, '-mos-transform': deg, '-o-transform': deg, 'transform': deg }); } else { $wrap.css( 'filter', 'progid:DXImageTransform.Microsoft.BasicImage(rotation='+ (~~((file.rotation/90)%4 + 4)%4) +')'); } }); $li.appendTo( $queue ); } // 负责view的销毁 function removeFile( file ) { var $li = $('#'+file.id); delete percentages[ file.id ]; updateTotalProgress(); $li.off().find('.file-panel').off().end().remove(); } function updateTotalProgress() { var loaded = 0, total = 0, spans = $progress.children(), percent; $.each( percentages, function( k, v ) { total += v[ 0 ]; loaded += v[ 0 ] * v[ 1 ]; } ); percent = total ? loaded / total : 0; spans.eq( 0 ).text( Math.round( percent * 100 ) + '%' ); spans.eq( 1 ).css( 'width', Math.round( percent * 100 ) + '%' ); updateStatus(); } function updateStatus() { var text = '', stats; if ( state === 'ready' ) { text = '选中' + fileCount + '张图片,共' + WebUploader.formatSize( fileSize ) + '。'; } else if ( state === 'confirm' ) { stats = uploader.getStats(); if ( stats.uploadFailNum ) { text = '已成功上传' + stats.successNum+ '张照片至服务器,'+ stats.uploadFailNum + '张照片上传失败,<a class="retry" href="#">重新上传</a>失败图片或<a class="ignore" href="#">忽略</a>' } } else { stats = uploader.getStats(); text = '共' + fileCount + '张(' + WebUploader.formatSize( fileSize ) + '),已上传' + stats.successNum + '张'; if ( stats.uploadFailNum ) { text += ',失败' + stats.uploadFailNum + '张'; } } $info.html( text ); } function setState( val ) { var file, stats; if ( val === state ) { return; } $upload.removeClass( 'state-' + state ); $upload.addClass( 'state-' + val ); state = val; switch ( state ) { case 'pedding': $placeHolder.removeClass( 'element-invisible' ); $queue.parent().removeClass('filled'); $queue.hide(); $statusBar.addClass( 'element-invisible' ); uploader.refresh(); break; case 'ready': $placeHolder.addClass( 'element-invisible' ); $( '#filePicker2' ).removeClass( 'element-invisible'); $queue.parent().addClass('filled'); $queue.show(); $statusBar.removeClass('element-invisible'); uploader.refresh(); break; case 'uploading': $( '#filePicker2' ).addClass( 'element-invisible' ); $progress.show(); $upload.text( '暂停上传' ); break; case 'paused': $progress.show(); $upload.text( '继续上传' ); break; case 'confirm': $progress.hide(); $upload.text( '开始上传' ).addClass( 'disabled' ); stats = uploader.getStats(); if ( stats.successNum && !stats.uploadFailNum ) { setState( 'finish' ); return; } break; case 'finish': stats = uploader.getStats(); if ( stats.successNum ) { $upload.text( '上传成功' ); //alert( '上传成功' ); } else { // 没有成功的图片,重设 state = 'done'; location.reload(); } break; } updateStatus(); } uploader.onUploadProgress = function( file, percentage ) { var $li = $('#'+file.id), $percent = $li.find('.progress span'); $percent.css( 'width', percentage * 100 + '%' ); percentages[ file.id ][ 1 ] = percentage; updateTotalProgress(); }; uploader.onFileQueued = function( file ) { fileCount++; fileSize += file.size; if ( fileCount === 1 ) { $placeHolder.addClass( 'element-invisible' ); $statusBar.show(); } addFile( file ); setState( 'ready' ); updateTotalProgress(); }; uploader.onFileDequeued = function( file ) { fileCount--; fileSize -= file.size; if ( !fileCount ) { setState( 'pedding' ); } removeFile( file ); updateTotalProgress(); }; uploader.on( 'all', function( type ) { switch( type ) { case 'uploadFinished': setState( 'confirm' ); break; case 'startUpload': setState( 'uploading' ); break; case 'stopUpload': setState( 'paused' ); break; } }); uploader.on( 'uploadSuccess', function( file,response ) { var resultStr = response.data; //alert(resultStr); $( '#orcResult' ).html(resultStr); }); uploader.onError = function( code ) { if(code == 'Q_EXCEED_NUM_LIMIT'){ alert( 'Eroor: 只允许上传一张图!' ); }else{ alert( 'Eroor: ' + code ); } }; $upload.on('click', function() { if ( $(this).hasClass( 'disabled' ) ) { return false; } if ( state === 'ready' ) { uploader.upload(); } else if ( state === 'paused' ) { uploader.upload(); } else if ( state === 'uploading' ) { uploader.stop(); } }); $info.on( 'click', '.retry', function() { uploader.retry(); } ); $info.on( 'click', '.ignore', function() { alert( 'todo' ); } ); $upload.addClass( 'state-' + state ); updateTotalProgress(); });
css用的bootstrap的及百度webuploader 中demo自带的css,这里就不贴出来了。
二、后端代码
这里因有涉及项目代码,不便贴全,至贴出代码实现主体部分。
接受前端页面的图片数据保存至服务器:
OcrController.java
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartHttpServletRequest; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
public void savePic(HttpServletRequest request, HttpServletResponse response){ AjaxRetBean<Object> returnBean = new AjaxRetBean<> (); try { try { Thread.sleep(1000); } catch(InterruptedException ex) { Thread.currentThread().interrupt(); } String picture = ""; MultipartHttpServletRequest muRequest = (MultipartHttpServletRequest)request; Map<String, MultipartFile> files = muRequest.getFileMap();//得到文件map对象 String uploadUrl = request.getSession().getServletContext().getRealPath("/")+"upload/";//得到当前工程路径拼接上文件名 File dir = new File(uploadUrl); if(!dir.exists())//目录不存在则创建 dir.mkdirs(); for(MultipartFile file :files.values()){ String fileName= new Date().getTime() + "_" + file.getOriginalFilename(); picture = uploadUrl+fileName; File upFile = new File(uploadUrl+fileName);//创建文件对象 if(!upFile.exists()){//文件名不存在 则新建文件,并将文件复制到新建文件中 upFile.createNewFile(); file.transferTo(upFile); } } String str = AipOcrUtil.UploadFile(picture); returnBean.setSuccess(true); returnBean.setData(str); } catch (IOException e) { logger.error("文件上传错误:"+e.getMessage()); } catch (Exception e){ logger.error(e.getMessage()); returnBean.setSuccess(false); returnBean.setMessage(e.getMessage()); } ResWriteUtil.writeObject(response,returnBean); }
AipOcrUtil.java
直接使用百度OCR提供的Api接口,接口详细:百度OCR-java
import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.baidu.aip.ocr.AipOcr; import org.json.JSONException; import java.util.HashMap; /** * Author: IrisNew * Description:百度Ocr文字识别 * Date: 2018/5/8 15:23 */ public class AipOcrUtil { //设置APPID/AK/SK 需自行去百度申请哦 private static final String APP_ID = "xxxx"; private static final String API_KEY = "xxxxxx"; private static final String SECRET_KEY = "xxxxxxx"; public static String UploadFile(String path) throws JSONException { // 初始化一个AipOcr AipOcr client = new AipOcr(APP_ID, API_KEY, SECRET_KEY); // 可选:设置网络连接参数 client.setConnectionTimeoutInMillis(2000); client.setSocketTimeoutInMillis(60000); // 调用接口 String resultStr = ""; org.json.JSONObject res = client.basicGeneral(path, new HashMap<String, String>()); JSONObject jsonObject = JSONObject.parseObject(res.toString()); JSONArray resultList = jsonObject.getJSONArray("words_result"); for(Object result:resultList){ JSONObject temp = (JSONObject) result; resultStr += temp.getString("words")+"<br/>"; } return resultStr; } }
至此,大功告成。看下效果吧。
三、效果展示
将这个图贴上试试
成功!
央视网消息:“绿水青山就是金山银山”,这是*对绿色发展理念的一句经典阐述,如 今这一理念已然沁入民心,深入生活,乃至造福世界 这句话是科学论断,是治国方略,更是中国智慧的体现和中国方案的亮点,让世界瞩目 萌芽,始于安吉 2005年8月15日,时任淅江省委书记的*来到湖州N市安吉县天荒坪镇余村,他以充满 前瞻性的战略眼光首次提出“绿水青山就是金山银山 周后的8月24日,*在《淅江日报》发表专栏评 绿水青山也是金山银山 在文中,*这样说 我们追求人与自然的和谐,经济与社会的和谐,通俗地讲,就是既要绿水青山,又 |