二维码编码与解码

前几天看了二维码生成和解码记录下,二维码作为现在替代网址输入方式的工具,在客户端中使用非常多,主要是将一段URL转化为一张正方形黑白相间的图片,通过对方扫描图面再转化为URL。本质上是一种信息编码解码工具。其中比较成熟的工具库包括:qrencode用于将一段字符转为二维码,zbar用于将二维码转为一段字符。

1.二维码编码与解码

国外有位作者博客文章非常详细的介绍二维码是如何生成的:

  • 二维码有40尺寸,最低尺寸为21x21,每涨一个尺寸,增加4
  • 二维码有三个定位图案,定位图案用于标定一个矩形(直角三角形推出另外一个角)
  • 二维码数据中有数据码和纠正码,字符会通过本身Ascll码转化为二进制编码排序,按8bits一组,纠错码用于纠错和补全全部编码量,纠错编码通过查表可以得到
  • 最终编码,由于字符即使通过数据码和纠正码转化,也是一行二进制数据,而二维码是二维数据,此时通过codewords重新将二进制数据重新排位

)

2.二维码编码库qrencode

2.1安装

  • ubuntu下 sudo apt-get install libqrencode libqrencode-dev
  • ubutnu 下编译下载:qrencode-4.0.2.tar.gz
    1
    2
    3
    4
    5
    6
    tar -zxvf qrencode-4.0.2.tar.gz
    cd qrencode-4.0.2.tar.gz
    sudo apt-get install autoconf automake libtool libpng12-dev autotools-dev
    ./configure
    make
    sudo make install

2.2使用

由于qrencode生成的图片数据不是cv上可以使用的数据结构,因此要转换一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
#include <qrencode.h>
#include <opencv2/opencv.hpp>
void string2image(std::string &str){
QRcode *pQRC;
unsigned char* p_qsource;
cv::Mat pic;
//依次是编码数据、版本、纠错等级、模式
pQRC=QRcode_encodeString(str.data(),0,QR_ECLEVEL_H, QR_MODE_8, 1);
if( pQRC!=NULL){
std::cout<<"success"<<std::endl;
int size=pQRC->width;
std::cout<<pQRC->width<<std::endl;
p_qsource=pQRC->data;
int widthAdjust=size*8;
// if(widthAdjust%4)widthAdjust=(widthAdjust/4+1)*4;
pic=cv::Mat::zeros(widthAdjust,widthAdjust,CV_8UC1);
//转换数据
for(int i=0;i<size;i++)
for(int j=0;j<size;j++){
if(!(p_qsource[i*size+j]&0x01)){
for(int k=0;k<8;k++)
for(int m=0;m<8;m++)
pic.at<char>(i*8+k,j*8+m)=0xff;
}
}
//增加外部一圈,因为二维码依靠外部三个同心方块定位
cv::copyMakeBorder(pic, pic, 8, 8, 8, 8,cv::BORDER_CONSTANT, cv::Scalar(255, 255, 255));
cv::imshow("demo", pic);
cv::imwrite(str+"_qrencode.jpg", pic);
cv::waitKey(0);
}
QRcode_free(pQRC);
}
int main(void){
string2image("hello,world");
return 0;
}

2.3编译使用

  • g++ qrencode.cpp -lqrencode -o qrencode
  • cmake 工程使用:qrencode-config.cmake文件,在Cmakefile.txt文件中添加FIND_PACKAGE(LibQRENCODE REQUIRED)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    find_path( LIBQRENCODE_INCLUDE_DIR qrencode.h
    PATHS $ENV{LIBQRENCODE_INCLUDE_DIR}
    )
    find_library( LIBQRENCODE_LIBRARY qrencode
    PATHS $ENV{LIBQRENCODE_LIBRARY}
    )
    mark_as_advanced( LIBQRENCODE_INCLUDE_DIR LIBQRENCODE_LIBRARY )

    set( LIBQRENCODE_INCLUDE_DIRS ${LIBQRENCODE_INCLUDE_DIR} )
    set( LIBQRENCODE_LIBRARIES ${LIBQRENCODE_LIBRARY} )

    include( FindPackageHandleStandardArgs )
    # handle the QUIETLY and REQUIRED arguments and set LIBQRENCODE_FOUND to TRUE
    # if all listed variables are TRUE
    find_package_handle_standard_args( LibQRENCODE DEFAULT_MSG LIBQRENCODE_INCLUDE_DIR LIBQRENCODE_LIBRARY )

3.二维码解码库zbar

3.1安装

3.2使用

由于拍摄时二维码不一定为刚好与图片四边都平行,因此需要调整二维码姿态,下面不做简绍,仅说明定位二维码,其中定位的方块有两层,如果求其包围边缘就有5条,同时findContours计算出的边缘信息可获得包围边缘的父子关系,因此考虑内外同心包围的5层图案即是定位点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
//计算点对之间距离
double getDistance(cv::Point p0,cv::Point p1){
double dx=p0.x-p1.x;
double dy=p0.y-p1.y;
return std::sqrt(dx*dx+dy*dy);
}
//计算中心
void check_center(std::vector<std::vector<cv::Point> > &c,std::vector<int> &index){
float dmin1=10000.0;
float dmin2=10000.0;
for(int i=0;i<c.size();i++){
auto rect_i=cv::minAreaRect(c[i]);
for(int j=i+1;j<c.size();j++){
auto rect_j=cv::minAreaRect(c[j]);
float d=getDistance(rect_i.center,rect_j.center);
if(d<dmin2&&d>10){
if(d<dmin1){
dmin2=dmin1;
dmin1=d;
index[2]=index[0];
index[3]=index[1];
index[0]=i;
index[1]=j;
}else{
dmin2=d;
index[2]=i;
index[3]=j;
}
}
}

}

}

void image2string(cv::Mat src){
cv::Mat ROI_image;
cv::cvtColor(src, ROI_image, CV_BGR2GRAY);
zbar::ImageScanner scanner;
scanner.set_config(zbar::ZBAR_NONE, zbar::ZBAR_CFG_ENABLE, 1);
uchar *raw=(uchar *)(ROI_image.data);
zbar::Image image(ROI_image.cols,ROI_image.rows,"Y800",raw,ROI_image.rows*ROI_image.cols);
scanner.scan(image);
if(image.symbol_begin()==image.symbol_end()){
std::cout<<"failure"<<std::endl;
}
for(auto symbol=image.symbol_begin();symbol!=image.symbol_end();++symbol){
std::cout<<symbol->get_data()<<std::endl;
}
}
int main(void){
int main(int argc, char const *argv[])
{
cv::Mat canny_image;
cv::Mat image=cv::imread("helloworld_qrencode.jpg");
cv::Canny(image, canny_image, 50, 100);//求其边缘

std::vector<std::vector<cv::Point> >contours;
std::vector<cv::Vec4i> hierarchy;
//寻找边
cv::findContours(canny_image, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);

std::vector<int> found;
std::vector<std::vector<cv::Point> >found_contours;

for(int i=0;i<contours.size();i++){
int k=i,c=0;
while(hierarchy[k][4]!=-1){
k=hierarchy[k][5];
c=c+1;
}
if(c>=5){//5条即为一个可能的定位点
found_contours.push_back(contours[i]);
found.push_back(i);
}
}
if(found.size()>=3){//可能有误检测的方块 已确定定位点
std::vector<int> index(4,-1);
check_center(found_contours,index);//寻找正角

//考虑变换

std::vector<cv::Point> final;
for(int i=0;i<4;i++){
auto part_rect=cv::minAreaRect(found_contours[index[i]]);
cv::Point2f p[4];
part_rect.points(p);
for(int j=0;j<4;j++)final.push_back(p[j]);
}
auto ROI=cv::boundingRect(final);
if(ROI.tl().x>0&&ROI.tl().y>0){
cv::Mat ROI_image=image(ROI);
image2string(ROI_image);//图片转URL
}
}
// cv::imshow("image", image);
// cv::imshow("canny", canny_image);
// cv::waitKey(0);
return 0;
}

3.3编译使用

  • g++ qrencode.cpp -lzbar -o qrencode
  • cmake工程使用,由于自带了cmake的文件,所以在Cmakefile.txt文件中直接添加FIND_PACKAGE(zbar REQUIRED)

hello