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

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

最近有接触到针对二维码进行识别的功能,然后对图片进行位置纠正、二维码扫描,并将数据进行整理。以下是通过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比较简单,直接安装就可以了,放个imagemagick 官网地址
这里面有一个小坑,安装程序的时候,一定要选中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

相关文章
|
4月前
|
存储 机器人 测试技术
AprilTags二维码的检测与应用
AprilTags二维码的检测与应用
430 0
|
5月前
|
人工智能 文字识别 开发工具
印刷文字识别使用问题之是否支持识别并返回文字在图片中的位置信息
印刷文字识别产品,通常称为OCR(Optical Character Recognition)技术,是一种将图像中的印刷或手写文字转换为机器编码文本的过程。这项技术广泛应用于多个行业和场景中,显著提升文档处理、信息提取和数据录入的效率。以下是印刷文字识别产品的一些典型使用合集。
|
5月前
|
编解码 JSON 文字识别
印刷文字识别使用问题之进行表格解析时年份和灰色部分没解析出来,网站体验能检测到,该如何优化
印刷文字识别产品,通常称为OCR(Optical Character Recognition)技术,是一种将图像中的印刷或手写文字转换为机器编码文本的过程。这项技术广泛应用于多个行业和场景中,显著提升文档处理、信息提取和数据录入的效率。以下是印刷文字识别产品的一些典型使用合集。
|
7月前
|
文字识别
分享:如何ocr识别身份证复印件并导出至excel表格 ? 图片批量识别导出excel表格应用,图片批量识别转excel表格的方法
该软件是一款OCR身份证识别工具,能批量处理图片,自动提取身份证信息并导出为Excel。支持百度网盘和腾讯云盘下载。用户界面直观,操作简单,适合新手。识别过程包括:打开图片、一键识别、导出结果。特别注意,此程序仅适用于身份证识别,不适用于其他类型的图片识别。
292 1
分享:如何ocr识别身份证复印件并导出至excel表格 ? 图片批量识别导出excel表格应用,图片批量识别转excel表格的方法
|
6月前
|
文字识别 安全 API
印刷文字识别产品使用合集之不想通过读取图片二进制文件的方式来传入图片内容,如何进行图片内容识别
印刷文字识别产品,通常称为OCR(Optical Character Recognition)技术,是一种将图像中的印刷或手写文字转换为机器编码文本的过程。这项技术广泛应用于多个行业和场景中,显著提升文档处理、信息提取和数据录入的效率。以下是印刷文字识别产品的一些典型使用合集。
|
7月前
|
文字识别 测试技术 数据安全/隐私保护
案例:批量区域识别内容重命名,批量识别扫描PDF区域内容识别重命名,批量识别图片区域内容重命名图片修改图片名字,批量识别图片区域文字并重命名,批量图片部分识别内容重命文件,PDF区域内容提取重命名
该内容介绍了如何使用区域识别重命名软件高效整理图片,例如将图片按时间及内容重命名,适用于简历、单据等识别。文中提供了软件下载链接(百度云盘和腾讯网盘),并列出软件使用的几个关键条件,包括文字清晰、文件名长度限制等。示例展示了银行单据和公司工作单据的识别情况。文章还提及OCR技术在图片文字识别中的应用,强调了识别率、误识率和用户友好性等评估指标。如有类似需求,读者可留言或下载软件测试,并提供图片以获取定制的识别方案。
358 2
|
7月前
|
JSON 文字识别 数据可视化
印刷文字识别产品使用合集之有识别二维码并将识别二维码的内容通过接口返回的功能吗
印刷文字识别(Optical Character Recognition, OCR)技术能够将图片、扫描文档或 PDF 中的印刷文字转化为可编辑和可搜索的数据。这项技术广泛应用于多个领域,以提高工作效率、促进信息数字化。以下是一些印刷文字识别产品使用的典型场景合集。
|
7月前
|
开发工具 数据安全/隐私保护 Android开发
视觉智能平台常见问题之图片解析出的水印图判断是自己添加的水印图如何解决
视觉智能平台是利用机器学习和图像处理技术,提供图像识别、视频分析等智能视觉服务的平台;本合集针对该平台在使用中遇到的常见问题进行了收集和解答,以帮助开发者和企业用户在整合和部署视觉智能解决方案时,能够更快地定位问题并找到有效的解决策略。
105 1
|
7月前
|
机器学习/深度学习 API Android开发
视觉智能平台常见问题之判断摄像头抓拍到包含人脸的照片如何解决
视觉智能平台是利用机器学习和图像处理技术,提供图像识别、视频分析等智能视觉服务的平台;本合集针对该平台在使用中遇到的常见问题进行了收集和解答,以帮助开发者和企业用户在整合和部署视觉智能解决方案时,能够更快地定位问题并找到有效的解决策略。
|
人工智能 文字识别 API
20行代码教你如何批量提取图片中文字
大家好,我是志斌~ 之前志斌在考研的时候遇到了一个问题,就是要将图片中的文字给提取出来,当时是J哥帮忙搞出来的,现在已经考完研了,也学会了提取方式,现在来给大家分享一下。
866 0
20行代码教你如何批量提取图片中文字

热门文章

最新文章