记录一次识别图片中二维码的过程

记录一次识别图片中二维码的过程

月光魔力鸭

2018-09-12 10:24 阅读 1447 喜欢 0 二维码 识别 nodejs

背景

最近有接触到针对二维码进行识别的功能,然后对图片进行位置纠正、二维码扫描,并将数据进行整理。以下是通过nodejs做的,相对简单一些,没有太过严格,识别率也不是很高大约80%左右。

思路
  1. 对FTP的目录进行监听,上传图片后,对图片进行处理。
  2. 二维码的位置,在图片的右上角和右下角,因此可以通过切图进行位置的确定,然后对图片进行旋转。
  3. 将图片切图后,对每一片进行二维码扫描,并获得二维码的内容
  4. 将获得的数据通过接口传递到API中,同时上传压缩后的图片
分步进行

一、FTP目录监听 监听这里我用的是chokidar,代码如下:

var chokidar = require('chokidar');
var config = require('./data');
var Util = require('./Util');//工具类,下面放代码
Util.log('服务器启动,开始对目标目录进行监听:'+config.ftp);
var watcherIns = null;
watcherIns = chokidar.watch(config.ftp,{
	persistent : true
});
watcherIns.on('add',function(filePath){
	//获得图片,再查找data.json 的内容,配置文件在data.json中
	Util.log('监测到有文件添加进入,文件路径为:'+filePath);
	var newName = Util.guid(filePath);//获得名称
	Util.log('随机获得文件的新名字:'+newName)
	exeFileOpt(newName,filePath);
});
function loopExe (){
	var fileName = Util.getError();
	if(fileName){
		Util.log('处理错误信息:['+fileName+']');
		exeFileOpt(fileName);
	}

	setTimeout(function(){
		loopExe();	
	},10000);//每5s执行一次,超过三次放弃
}
loopExe();//执行

function exeFileOpt ( newName,filePath ){
	if(filePath){
		var createTime= Util.getBirthTime(filePath);
		Util.log('['+newName+']获得文件创建时间:'+createTime);
		Util.cache[newName].time = createTime;
	}
	
	//3.对图片进行分割:4部分,r/b/l/t--右上角、右下角、左下角、左上角;
	async.waterfall([
		function(callback){
			Util.split(newName,callback);
		},
		function(fileName,callback){
			Util.rotate(fileName,callback);
		},
		function(fileName,callback){
			Util.copy(fileName,callback);
		},
		function(fileName,callback){
			Util.thumb(fileName,callback);
		},
		function(fileName,callback){
			Util.send(fileName,callback);
		}
	],function(err,result){
		if(err){
			console.log(err);
			Util.log('['+newName+']执行过程报错');
			Util.addError();
			Util.pushError(newName);
		}else{
			Util.log('['+newName+']执行完毕');
			Util.addSuccess();
			Util.deleteError(newName);
		}
	});
}

二、对图片进行切图,然后扫描二维码。其中,对于图片的处理,使用的是imagemagick,二维码识别使用的是qrcode-reader. 由于我这里使用的是windows系统,所以安装imagemagick比较简单,直接安装就可以了,放个undefined。 这里面有一个小坑,安装程序的时候,一定要选中install legacy utilities(e.g. convert),我安装的版本是7.

对于二维码识别,是基于zxing的nodejs版本,识别率不是很高,但是勉强可以,我是先把图片处理了一下,尽量提高识别率。 工具类代码:

/***
1.处理图片操作
2.处理二维码
3.处理位置
***/

var gm = require('gm');//图片操作
var magick = gm.subClass({imageMagick : true});//实体
// var magick =require('gm').subClass({ imageMagick : true });
// var gm = require('gm');
var fs = require('fs');//文件操作
var QrCode = require('qrcode-reader');//二维码读取
var Jimp = require('jimp');//bitmap
var moment = require('moment');//moment
var path = require('path');
var async = require('async');
var config = require('./data');
var superagent = require('superagent');

var Util = {
	cache : {},//存放每一个图片的具体信息
	error : {},//存放错误的具体信息
	delay : {}//存放延迟处理的文件夹集合
};


/**
 * 错误机制
 * 1. 将错误的图片放在内存中
 * 2. 定时去处理错误的信息(比如五分钟处理一次?或者一分钟检查一次)
 * 3. 将成功后的文件消灭掉,并更新
 **/


//添加错误信息
Util.pushError = function( fileName ){
	var info = Util.cache[fileName];
	if(info){
		//判断error中是否存在
		if(Util.error[fileName]){
			info.error = info.error + 1;
			Util.error[fileName] = info;
			if(info.error > 3){
				Util.deleteError(fileName);
			}
		}else{
			info.error = 1;
			Util.error[fileName] = info;
		}
	}
};
//删除错误信息
Util.deleteError = function( fileName ){
	var info = Util.error[fileName];
	if(info){
		Util.error[fileName] = null;
		delete Util.error[fileName];
	}
};
//获得第一个错误信息
Util.getError = function(){
	var first = null;
	for(var key in Util.error){
		if(key && Util.error[key]){
			first =key;
			break;
		}
	}
	return first;
};

//成功+1
Util.addSuccess = function(){
	var data = require('./analysis');
	var all = data.all || 0;
	all ++ ;
	var now = moment(new Date()).format('YYYY-MM-DD');
	var today = data[now] || 0;
	today ++ ;
	data.all = all;
	data[now] = today;
	fs.writeFileSync('./analysis.json',JSON.stringify(data));
};
//失败+1
Util.addError = function(){
	var data = require('./analysis');
	var all = data.all || 0;
	all ++ ;
	var error = data.error || 0;
	error ++ ;
	data.all = all;
	data.error = error;
	fs.writeFileSync('./analysis.json',JSON.stringify(data));
}
//日志处理
Util.log = function(str){
	var now = moment(new Date()).format('YYYY-MM-DD HH:mm:ss');
	//1.打印
	str = typeof str === 'string' ? str : str.toString();
	var info = now +'\t'+str+'\n';
	// console.log(info);
	//2.写入文件
	var nowDate = moment(new Date()).format('YYYY-MM-DD');
	var logPath = path.join(__dirname,'logs',nowDate+'.log');
	if(fs.existsSync(logPath)){
		fs.appendFile(logPath,info,function(err){
			if(err){
				console.log(err);
			}
		});
	}else{
		fs.writeFile(logPath,info,function(err){
			if(err){
				console.log(err);
			}
		});
	}
};
//对时间进行格式化
Util.format = function(time){
	return moment(time).format('YYYY-MM-DD HH:mm:ss');
};
//获得文件的创建时间
Util.getBirthTime = function(filePath){
	var stats = fs.statSync(filePath);
	return Util.format(stats.birthtime);
};
//获得随机ID
Util.guid = function(filePath){
	var guid = (+new Date()).toString( 32 ),i = 0;
    for ( ; i < 5; i++ ) {
        guid += Math.floor( Math.random() * 65535 ).toString( 32 );
    }
    while(Util.cache[guid]){
    	guid = Util.guid();
    }
    if(filePath){
    	Util.cache[guid] = {
    		filePath : filePath,
    		extname : path.extname(filePath)
    	};
    }
    return guid;
};
//创建目录
Util.mkdirs = function( dirpath ){
	dirpath = path.dirname(dirpath);
	 try{
        if (!fs.existsSync(dirpath)) {
            var pathtmp;
            dirpath.split(/[/\\]/).forEach(function (dirname) {  //这里指用/ 或\ 都可以分隔目录  如  linux的/usr/local/services   和windows的 d:\temp\aaaa
                if (pathtmp) {
                    pathtmp = path.join(pathtmp, dirname);
                }else {
                    pathtmp = dirname;
                }
                if (!fs.existsSync(pathtmp)) {
                    if (!fs.mkdirSync(pathtmp, 0777)) {
                        return false;
                    }
                }
            });
        }
        return true; 
    }catch(e){
    	Util.log('文件夹目录创建失败:'+dirpath+'\n'+e.toString());
        return false;
    }
}
//对图片进行压缩
Util.thumb = function( fileName ,callback){
	var info = Util.cache[fileName];
	//将图片压缩
	var filePath = info.filePath;
	var date = moment(new Date()).format('YYYYMMDD');
	var extname = path.extname(filePath);
	//目标路径
	var newPath = '/byymoral/'+date+'/'+fileName+'-thumb'+extname;
	var targetPath = path.join(config.target,newPath);
	magick(filePath).resize(config.thumbWidth).density(config.dpi).write(targetPath,function(err){
		if(err){
			Util.log(err.toString());
		}
		info.thumb = newPath;
		Util.cache[fileName] = info;
		callback(null,fileName);
	});
};
//复制图片
Util.copy = function(fileName,callback){
	var info = Util.cache[fileName];
	var filePath = info.filePath;
	//给定时间
	var date = moment(new Date()).format('YYYYMMDD');
	var extname = path.extname(filePath);

	var newPath = '/byymoral/'+date+'/'+fileName+extname;
	//如果路径不存在,则创建

	Util.log('['+fileName+']移动后新路径为'+newPath);
	info.newPath = newPath;
	Util.cache[fileName] = info;
	//将图片移动到某位置
	var movePath = path.join(config.target,newPath);
	if(Util.mkdirs(movePath)){
		//复制过去,压缩后
		magick(filePath).density(config.dpi).quality(config.compressQuality).write(movePath,function(err){
			if(err){
				var data = fs.readFileSync(filePath);
				fs.writeFileSync(movePath,data);
				Util.log(err.toString());
			}
			Util.log('['+fileName+']复制成功,由'+filePath+'复制到'+newPath);
			callback(null,fileName);
		});
	}
};
//对buffer读取二维码内容
Util.readSimple = function(buffer){
	var qr = new QrCode();
    qr.callback = function(err2, value) {
        if (err2) {
        	//没有二维码
        	Util.log('['+fileName+']'+(['右上角','右下角','左下角','左上角'][pos])+'无二维码');
        	rs.has = false;
        	rs.msg = '图片没有二维码';
        	cb(null,rs);
        }else{
        	rs.has = true;
        	Util.log('['+fileName+']'+(['右上角','右下角','左下角','左上角'][pos])+'二维码内容:'+value.result);
        	rs.result = value.result;
	        //根据宽高计算位置和大小
	        cb(null,rs);
        }
    };
    qr.decode(blockimg.bitmap);
}
//识别二维码
Util.readCode = function(fileName,filePath,pos,cb){
	var rs = {
		has : false,
		pos : pos
	};
	//读取图片是否有二维码
	var buffer = fs.readFileSync(filePath);
	fs.unlinkSync(filePath);
	Jimp.read(buffer).then(function(blockimg){
		var qr = new QrCode();
	    qr.callback = function(err2, value) {
	        if (err2) {
	        	//没有二维码
	        	Util.log('['+fileName+']'+(['右上角','右下角','左下角','左上角'][pos])+'无二维码');
	        	rs.has = false;
	        	rs.msg = '图片没有二维码';
	        	cb(null,rs);
	        }else{
	        	rs.has = true;
	        	Util.log('['+fileName+']'+(['右上角','右下角','左下角','左上角'][pos])+'二维码内容:'+value.result);
	        	rs.result = value.result;
		        //根据宽高计算位置和大小
		        cb(null,rs);
	        }
	    };
	    qr.decode(blockimg.bitmap);
	}).catch(function(err2){
		if(err2){
			Util.log(err2.toString());
			cb(err2);
		}
	});
};
//对图片进行裁切
Util.crop = function(fileName,filePath,targetPath,width,height,x,y,pos,callback){
	magick(filePath).crop(width,height,x,y)
	.contrast(0)
	.resize(1500)
	// .sharpen(0,10)
	// .whiteThreshold(50,50,50)
	.threshold('75%')
	.write(targetPath,function(err){
		if(err){
			Util.log(err.toString());
			callback(err);
		}else{
			Util.log('['+fileName+']截取'+(['右上角','右下角','左下角','左上角'][pos])+'图片成功');
			Util.readCode(fileName,targetPath,pos,function(err2,rs){
				callback(err2,rs);
			});
		}
	})
};
//获得旋转度数
Util.getDegree = function( arr ){
	//四个角,右上角和右下角有--,可能存在的情况 01 12 23 30,如果是单独的则是0 1 2 3 
	var target = ['01','12','23','03'];
	var num = 0;
	var ts = [];
	var str = [];//内容
	//排序
	arr.sort(function(a,b){
		return a.pos > b.pos;
	});
	arr.forEach(function( item ){
		if(item.has){
			ts.push(item.pos);
			str.push(item.result);
			num ++ ;
		}
	});
	//
	if(num == 1){//只有一个,肯定在右上角
		var temp = ts[0];
		var rs = {
			degree : -temp * 90,
			result : str
		};
		return rs;
	}else if(num == 2){
		var temp = ts.sort().join('');
		if(target.indexOf(temp)>-1){
			var rs = {
				degree : - target.indexOf(temp) * 90,
				result : temp === '03' ? str.reverse() : str
			};
			return rs;
		}
	}
	return {
		degree : 0,
		result : str
	};
};
//整理数据,提交到接口
Util.send = function(fileName,callback){
	/***
	 * 整理数据:
	 * 1.图片的创建时间
	 * 2.图片转移后的路径
	 * 3.二维码的内容
	 * 4.
	 ***/
	 var info = Util.cache[fileName];
	 var degree = info.degree;
	 var result = [''];
	 if(degree.result){
	 	result = degree.result;
	 }
	 var data = {
	 	createTime : info.time,
	 	content0 : result[0],
	 	content1 : result.length > 1 ? result[1] : '',
	 	filePath : info.newPath,
	 	thumb : info.thumb
	 };
	 Util.log('['+fileName+']调用接口发送数据:'+JSON.stringify(data));
	 superagent
	 .post(config.api)
	 // .send(data)
	 .field('createTime',info.time)
	 .field('content0',result[0])
	 .field('content1',result.length >1 ? result[1] : '')
	 //attach file
	 .attach('file',path.join(config.target,info.newPath))
	 .attach('thumb',path.join(config.target,info.thumb))
	 .end(function(err,res){
	 	if(err){
	 		Util.log('['+fileName+']调用接口失败'+err.toString());
	 		callback(err);
	 	}else{
	 		// var txt = res.text;
	 		Util.log('['+fileName+']调用接口成功');
	 		//删除源文件
	 		fs.unlinkSync(info.filePath);
	 		Util.log('['+fileName+']删除源文件成功'+info.filePath);
	 		callback(null);
	 	}
	 });
}
//旋转
Util.rotate = function(fileName,callback){
	var info = Util.cache[fileName];
	if(info && info.degree){
		var filePath = info.filePath,degree = info.degree.degree;
		if(degree !== 0){
			magick(filePath).rotate('width',degree).write(filePath,function(err){
				if(err){
					Util.log('['+fileName+']旋转图片角度失败');
					callback(err);//即便是失败也要上传
				}else{
					Util.log('['+fileName+']旋转图片角度'+degree+'° 成功');
					callback(null,fileName);
				}
			});
		}else{
			callback(null,fileName);
		}
	}else{
		callback(null,fileName);	
	}
};
//图片分割并识别二维码
Util.split = function( fileName,callback ){
	var info = Util.cache[fileName];
	var filePath = info.filePath;
	//通过gm对文件进行分割成四个分片并保存
	setTimeout(function(){
		Jimp.read(filePath).then(function(image){
			var width = image.bitmap.width,height = image.bitmap.height;
			Util.log('['+fileName+']获得图片宽高:w='+width+'px;h='+height+'px');
			var hwidth = width /2 ,hheight = height /2 ;
			var targetPath = path.join(__dirname,'crop');
			var ext = path.extname(fileName);

			var rpath = path.join(targetPath,fileName+'-r'+info.extname);
			var bpath = path.join(targetPath,fileName+'-b'+info.extname);
			var lpath = path.join(targetPath,fileName+'-l'+info.extname);
			var tpath = path.join(targetPath,fileName+'-t'+info.extname);

			var hasCode = false;
			//同时进行
			async.parallel([
				function(callback){//右上角,根据宽高比对
					var a,b,c,d;
					if(width >= height){//横向
						a = width / 4;
						b = height /2 ;
						c = width /4 * 3;
						d = 0;
					}else{
						a = width /2;
						b = height /4;
						c = width /2;
						d = 0;
					}
					Util.crop(fileName,filePath,rpath,a,b,c,d,0,callback);
				},
				function(callback){//右下角
					var a,b,c,d;
					if(width >= height){//横向
						a = width /4;
						b = height /2;
						c = width /4 * 3;
						d = height /2;
					}else{
						a = width /2;
						b = height /4;
						c = width /2;
						d = height/4*3;
					}
					Util.crop(fileName,filePath,bpath,a,b,c,d,1,callback);
				},
				function(callback){//左下角
					var a,b,c,d;
					if(width >= height){//横向
						a = width /4;
						b = height /2;
						c = 0;
						d = height /2;
					}else{
						a = width /2;
						b = height /4;
						c = 0;
						d = height /4 *3;
					}
					Util.crop(fileName,filePath,lpath,a,b,c,d,2,callback);
				},
				function(callback){//左上角
					var a,b,c,d;
					if(width >= height){//横向
						a = width /4;
						b = height/2;
						c = 0;
						d = 0;
					}else{
						a = width /2;
						b = height/4;
						c = 0;
						d =0;
					}
					Util.crop(fileName,filePath,tpath,a,b,c,d,3,callback);
				}
			],function(err3,res){
				if(err3){
					Util.log(err3.toString());
					callback(err3);
				}else{
					var degree = Util.getDegree(res);//获得度数
					info.degree = degree;//设置度数信息和二维码内容
					Util.cache[fileName] = info;
					callback(null,fileName);	
				}
			});
		}).catch(function(err){
			if(err){
				Util.log(err.toString())
				callback(err);
			}
		});
	},500);
	

};

module.exports = Util;

核心代码已经在上面了,主要的处理就是:图片切割、二维码识别、图片处理、接口调用。 附上一张页面图: image.png

转载请注明出处: https://chrunlee.cn/article/scan-picture-code.html


感谢支持!

赞赏支持
提交评论
评论信息 (请文明评论)
暂无评论,快来快来写想法...
推荐
通过imap 来接收邮箱新邮件,类似客户端系列,不过比较简单的,目前只有新邮件,后续也可以通过这个来做一个自己的邮件客户端。
使用nodejs 连接mysql数据库还是很简单的,有现成的模块可以直接调用。下面介绍下 mysql 的调用
分享一个自动抓取静态站资源的小工具,可以在抓取某个静态站点的时候方便很多,尤其是如果页面比较多的话,会很难受,而且会自动将资源进行归类,如果一个页面一个页面的保存的话,那就比较费劲了。
这事其实很简单,就是调用adb几个命令而已.. 主要是有时候想用的时候总感觉差点啥,东拼西凑才弄出来,这里记录下,哪怕就是贴个地址的,也比记在脑子里强。
前几天给朋友帮忙,想要一个一模一样的网站...自告奋勇去帮忙.. 结果发现之前一直没处理过类似的情况,虽然也写过爬虫,不过看了下网站,也不算麻烦。于是简单实现了这个自动抓站的功能,最终整理成为一个自动抓站的工具,能省很多的事情。
目前了解的有两个模块可以实现二维码的模块,一个是node-qrcode ,这个算是比较大众的,不过环境比较复杂,所以...连看都没看;还有一个是小众的 qr-image ,这个比较简单,没有其他环境依赖,安装即可用,因为要实现一个简单的在线二维码生成,就先用这个试试水了
因为自己的记录笔记的应用是有道云,又想着把有道云跟自己的小网站联通起来,所以查找了有道云的,然后实现了nodejs版本的sdk.
最近一直在围绕着我的小电脑在转,基本都是这方面的问题。在没有公网IP的情况下,这个问题就是我怎么才能通过域名访问到我家中的服务器。