741 lines
22 KiB
C
741 lines
22 KiB
C
/*Copyright (C) 2008-2009 Timothy B. Terriberry (tterribe@xiph.org)
|
|
You can redistribute this library and/or modify it under the terms of the
|
|
GNU Lesser General Public License as published by the Free Software
|
|
Foundation; either version 2.1 of the License, or (at your option) any later
|
|
version.*/
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
#include <string.h>
|
|
#include "util.h"
|
|
//#include "type.h"
|
|
#include "image.h"
|
|
#include "binarize.h"
|
|
|
|
#if 0
|
|
/*Binarization based on~\cite{GPP06}.
|
|
@ARTICLE{GPP06,
|
|
author="Basilios Gatos and Ioannis E. Pratikakis and Stavros J. Perantonis",
|
|
title="Adaptive Degraded Document Image Binarization",
|
|
journal="Pattern Recognition",
|
|
volume=39,
|
|
number=3,
|
|
pages="317-327",
|
|
month=Mar,
|
|
year=2006
|
|
}*/
|
|
|
|
#if 0
|
|
/*Applies a 5x5 Wiener filter to the image, in-place, emphasizing differences
|
|
where the local variance is small, and de-emphasizing them where it is
|
|
large.*/
|
|
void qr_wiener_filter(unsigned char *_img,int _width,int _height){
|
|
unsigned *m_buf[8];
|
|
unsigned *sn2_buf[8];
|
|
unsigned char g;
|
|
int x;
|
|
int y;
|
|
if(_width<=0||_height<=0)return;
|
|
m_buf[0]=(unsigned *)malloc((_width+4<<3)*sizeof(*m_buf));
|
|
sn2_buf[0]=(unsigned *)malloc((_width+4<<3)*sizeof(*sn2_buf));
|
|
for(y=1;y<8;y++){
|
|
m_buf[y]=m_buf[y-1]+_width+4;
|
|
sn2_buf[y]=sn2_buf[y-1]+_width+4;
|
|
}
|
|
for(y=-4;y<_height;y++){
|
|
unsigned *pm;
|
|
unsigned *psn2;
|
|
int i;
|
|
int j;
|
|
pm=m_buf[y+2&7];
|
|
psn2=sn2_buf[y+2&7];
|
|
for(x=-4;x<_width;x++){
|
|
unsigned m;
|
|
unsigned m2;
|
|
m=m2=0;
|
|
if(y>=0&&y<_height-4&&x>=0&&x<_width-4)for(i=0;i<5;i++)for(j=0;j<5;j++){
|
|
g=_img[(y+i)*_width+x+j];
|
|
m+=g;
|
|
m2+=g*g;
|
|
}
|
|
else for(i=0;i<5;i++)for(j=0;j<5;j++){
|
|
g=_img[QR_CLAMPI(0,y+i,_height-1)*_width+QR_CLAMPI(0,x+j,_width-1)];
|
|
m+=g;
|
|
m2+=g*g;
|
|
}
|
|
pm[x+4]=m;
|
|
psn2[x+4]=(m2*25-m*m);
|
|
}
|
|
pm=m_buf[y&7];
|
|
if(y>=0)for(x=0;x<_width;x++){
|
|
int sn2;
|
|
sn2=sn2_buf[y&7][x+2];
|
|
if(sn2){
|
|
int vn3;
|
|
int m;
|
|
/*Gatos et al. give the expression
|
|
mu+(s2-v2)*(g-mu)/s2 ,
|
|
which we reduce to
|
|
mu+(s2-v2)*g/s2-(s2-v2)*mu/s2 ,
|
|
g-(v2/s2)*g+(v2/s2)*mu ,
|
|
g+(mu-g)*(v2/s2) .
|
|
However, s2 is much noisier than v2, and dividing by it often gives
|
|
extremely large adjustments, causing speckle near edges.
|
|
Therefore we limit the ratio (v2/s2) to lie between 0 and 1.*/
|
|
vn3=0;
|
|
for(i=-2;i<3;i++){
|
|
psn2=sn2_buf[y+i&7];
|
|
for(j=0;j<5;j++)vn3+=psn2[x+j];
|
|
}
|
|
m=m_buf[y&7][x+2];
|
|
vn3=vn3+1023>>10;
|
|
sn2=25*sn2+1023>>10;
|
|
if(vn3<sn2){
|
|
int a;
|
|
g=_img[y*_width+x];
|
|
a=(m-25*g)*vn3;
|
|
sn2*=25;
|
|
_img[y*_width+x]=QR_CLAMP255(g+QR_DIVROUND(a,sn2));
|
|
}
|
|
else _img[y*_width+x]=(unsigned char)(((m<<1)+25)/50);
|
|
}
|
|
}
|
|
}
|
|
free(sn2_buf[0]);
|
|
free(m_buf[0]);
|
|
}
|
|
|
|
#else
|
|
/*Applies a 3x3 Wiener filter to the image, in-place, emphasizing differences
|
|
where the local variance is small, and de-emphasizing them where it is
|
|
large.*/
|
|
void qr_wiener_filter(unsigned char *_img,int _width,int _height){
|
|
unsigned *m_buf[4];
|
|
unsigned *sn2_buf[4];
|
|
unsigned char g;
|
|
int x;
|
|
int y;
|
|
if(_width<=0||_height<=0)return;
|
|
m_buf[0]=(unsigned *)malloc((_width+2<<2)*sizeof(*m_buf));
|
|
sn2_buf[0]=(unsigned *)malloc((_width+2<<2)*sizeof(*sn2_buf));
|
|
for(y=1;y<4;y++){
|
|
m_buf[y]=m_buf[y-1]+_width+2;
|
|
sn2_buf[y]=sn2_buf[y-1]+_width+2;
|
|
}
|
|
for(y=-2;y<_height;y++){
|
|
unsigned *pm;
|
|
unsigned *psn2;
|
|
int i;
|
|
int j;
|
|
pm=m_buf[y+1&3];
|
|
psn2=sn2_buf[y+1&3];
|
|
for(x=-2;x<_width;x++){
|
|
unsigned m;
|
|
unsigned m2;
|
|
m=m2=0;
|
|
if(y>=0&&y<_height-2&&x>=0&&x<_width-2)for(i=0;i<3;i++)for(j=0;j<3;j++){
|
|
g=_img[(y+i)*_width+x+j];
|
|
m+=g;
|
|
m2+=g*g;
|
|
}
|
|
else for(i=0;i<3;i++)for(j=0;j<3;j++){
|
|
g=_img[QR_CLAMPI(0,y+i,_height-1)*_width+QR_CLAMPI(0,x+j,_width-1)];
|
|
m+=g;
|
|
m2+=g*g;
|
|
}
|
|
pm[x+2]=m;
|
|
psn2[x+2]=(m2*9-m*m);
|
|
}
|
|
pm=m_buf[y&3];
|
|
if(y>=0)for(x=0;x<_width;x++){
|
|
int sn2;
|
|
sn2=sn2_buf[y&3][x+1];
|
|
if(sn2){
|
|
int m;
|
|
int vn3;
|
|
/*Gatos et al. give the expression
|
|
mu+(s2-v2)*(g-mu)/s2 ,
|
|
which we reduce to
|
|
mu+(s2-v2)*g/s2-(s2-v2)*mu/s2 ,
|
|
g-(v2/s2)*g+(v2/s2)*mu ,
|
|
g+(mu-g)*(v2/s2) .
|
|
However, s2 is much noisier than v2, and dividing by it often gives
|
|
extremely large adjustments, causing speckle near edges.
|
|
Therefore we limit the ratio (v2/s2) to lie between 0 and 1.*/
|
|
vn3=0;
|
|
for(i=-1;i<2;i++){
|
|
psn2=sn2_buf[y+i&3];
|
|
for(j=0;j<3;j++)vn3+=psn2[x+j];
|
|
}
|
|
m=m_buf[y&3][x+1];
|
|
vn3=vn3+31>>5;
|
|
sn2=9*sn2+31>>5;
|
|
if(vn3<sn2){
|
|
int a;
|
|
g=_img[y*_width+x];
|
|
a=m-9*g;
|
|
sn2*=9;
|
|
_img[y*_width+x]=QR_CLAMP255(g+QR_DIVROUND(a,sn2));
|
|
}
|
|
else _img[y*_width+x]=(unsigned char)(((m<<1)+9)/18);
|
|
}
|
|
}
|
|
}
|
|
free(sn2_buf[0]);
|
|
free(m_buf[0]);
|
|
}
|
|
#endif
|
|
|
|
/*Computes a (conservative) foreground mask using the adaptive binarization
|
|
threshold given in~\cite{SP00}, but knocking the threshold parameter down to
|
|
k=0.2.
|
|
Note on dynamic range: we assume _width*_height<=0x1000000 (24 bits).
|
|
Returns the average background value.
|
|
@ARTICLE{SP00,
|
|
author="Jaakko J. Sauvola and Matti Pietik\"{a}inen",
|
|
title="Adaptive Document Image Binarization",
|
|
volume=33,
|
|
number=2,
|
|
pages="225--236",
|
|
month=Feb,
|
|
year=2000
|
|
}*/
|
|
static void qr_sauvola_mask(unsigned char *_mask,unsigned *_b,int *_nb,
|
|
const unsigned char *_img,int _width,int _height){
|
|
unsigned b;
|
|
int nb;
|
|
b=0;
|
|
nb=0;
|
|
if(_width>0&&_height>0){
|
|
unsigned *col_sums;
|
|
unsigned *col2_sums;
|
|
int logwindw;
|
|
int logwindh;
|
|
int windw;
|
|
int windh;
|
|
int y0offs;
|
|
int y1offs;
|
|
unsigned g;
|
|
unsigned g2;
|
|
int x;
|
|
int y;
|
|
/*We keep the window size fairly large to ensure it doesn't fit completely
|
|
inside the center of a finder pattern of a version 1 QR code at full
|
|
resolution.*/
|
|
for(logwindw=4;logwindw<8&&(1<<logwindw)<(_width+7>>3);logwindw++);
|
|
for(logwindh=4;logwindh<8&&(1<<logwindh)<(_height+7>>3);logwindh++);
|
|
windw=1<<logwindw;
|
|
windh=1<<logwindh;
|
|
col_sums=(unsigned *)malloc(_width*sizeof(*col_sums));
|
|
col2_sums=(unsigned *)malloc(_width*sizeof(*col2_sums));
|
|
/*Initialize sums down each column.*/
|
|
for(x=0;x<_width;x++){
|
|
g=_img[x];
|
|
g2=g*g;
|
|
col_sums[x]=(g<<logwindh-1)+g;
|
|
col2_sums[x]=(g2<<logwindh-1)+g2;
|
|
}
|
|
for(y=1;y<(windh>>1);y++){
|
|
y1offs=QR_MINI(y,_height-1)*_width;
|
|
for(x=0;x<_width;x++){
|
|
g=_img[y1offs+x];
|
|
col_sums[x]+=g;
|
|
col2_sums[x]+=g*g;
|
|
}
|
|
}
|
|
for(y=0;y<_height;y++){
|
|
unsigned m;
|
|
unsigned m2;
|
|
int x0;
|
|
int x1;
|
|
/*Initialize the sums over the window.*/
|
|
m=(col_sums[0]<<logwindw-1)+col_sums[0];
|
|
m2=(col2_sums[0]<<logwindw-1)+col2_sums[0];
|
|
for(x=1;x<(windw>>1);x++){
|
|
x1=QR_MINI(x,_width-1);
|
|
m+=col_sums[x1];
|
|
m2+=col2_sums[x1];
|
|
}
|
|
for(x=0;x<_width;x++){
|
|
int d;
|
|
/*Perform the test against the threshold T = (m/n)*(1+k*(s/R-1)),
|
|
where n=windw*windh, s=sqrt((m2-(m*m)/n)/n), and R=128.
|
|
We don't actually compute the threshold directly, as that would
|
|
require a square root.
|
|
Instead we perform the equivalent test:
|
|
(m/n)*(m/n)*(m2/n-(m/n)*(m/n))/16 > (((1/k)*g-((1-k)/k)*(m/n))*32)**2
|
|
R is split up across each side of the inequality to maximize the
|
|
dynamic range available for the right hand side, which requires
|
|
31 bits in the worst case.*/
|
|
/*(m/n)*(1+(1/5)*(sqrt((m2-m*m/n)/n)/128-1)) > g
|
|
m*(1+(1/5)*(sqrt((m2-m*m/n)/n)/128-1)) > g*n
|
|
m*sqrt((m2-m*m/n)/n) > 5*g*n-4*m<<7
|
|
m*m*(m2*n-m*m) > (5*g*n-4*m<<7)**2*n*n || 5*g*n-4*m < 0 */
|
|
g=_img[y*_width+x];
|
|
d=(5*g<<logwindw+logwindh)-4*m;
|
|
if(d>=0){
|
|
unsigned mm;
|
|
unsigned mms2;
|
|
unsigned d2;
|
|
mm=(m>>logwindw)*(m>>logwindh);
|
|
mms2=(m2-mm>>logwindw+logwindh)*(mm>>logwindw+logwindh)+15>>4;
|
|
d2=d>>logwindw+logwindh-5;
|
|
d2*=d2;
|
|
if(d2>=mms2){
|
|
/*Update the background average.*/
|
|
b+=g;
|
|
nb++;
|
|
_mask[y*_width+x]=0;
|
|
}
|
|
else _mask[y*_width+x]=0xFF;
|
|
}
|
|
else _mask[y*_width+x]=0xFF;
|
|
/*Update the window sums.*/
|
|
if(x+1<_width){
|
|
x0=QR_MAXI(0,x-(windw>>1));
|
|
x1=QR_MINI(x+(windw>>1),_width-1);
|
|
m+=col_sums[x1]-col_sums[x0];
|
|
m2+=col2_sums[x1]-col2_sums[x0];
|
|
}
|
|
}
|
|
/*Update the column sums.*/
|
|
if(y+1<_height){
|
|
y0offs=QR_MAXI(0,y-(windh>>1))*_width;
|
|
y1offs=QR_MINI(y+(windh>>1),_height-1)*_width;
|
|
for(x=0;x<_width;x++){
|
|
g=_img[y0offs+x];
|
|
col_sums[x]-=g;
|
|
col2_sums[x]-=g*g;
|
|
g=_img[y1offs+x];
|
|
col_sums[x]+=g;
|
|
col2_sums[x]+=g*g;
|
|
}
|
|
}
|
|
}
|
|
free(col2_sums);
|
|
free(col_sums);
|
|
}
|
|
*_b=b;
|
|
*_nb=nb;
|
|
}
|
|
|
|
/*Interpolates a background image given the source and a conservative
|
|
foreground mask.
|
|
If the current window contains no foreground pixels, the average background
|
|
value over the whole image is used.
|
|
Note on dynamic range: we assume _width*_height<=0x8000000 (23 bits).
|
|
Returns the average difference between the foreground and the interpolated
|
|
background.*/
|
|
static void qr_interpolate_background(unsigned char *_dst,
|
|
int *_delta,int *_ndelta,const unsigned char *_img,const unsigned char *_mask,
|
|
int _width,int _height,unsigned _b,int _nb){
|
|
int delta;
|
|
int ndelta;
|
|
delta=ndelta=0;
|
|
if(_width>0&&_height>0){
|
|
unsigned *col_sums;
|
|
unsigned *ncol_sums;
|
|
int logwindw;
|
|
int logwindh;
|
|
int windw;
|
|
int windh;
|
|
int y0offs;
|
|
int y1offs;
|
|
unsigned b;
|
|
unsigned g;
|
|
int x;
|
|
int y;
|
|
b=_nb>0?((_b<<1)+_nb)/(_nb<<1):0xFF;
|
|
for(logwindw=4;logwindw<8&&(1<<logwindw)<(_width+15>>4);logwindw++);
|
|
for(logwindh=4;logwindh<8&&(1<<logwindh)<(_height+15>>4);logwindh++);
|
|
windw=1<<logwindw;
|
|
windh=1<<logwindh;
|
|
col_sums=(unsigned *)malloc(_width*sizeof(*col_sums));
|
|
ncol_sums=(unsigned *)malloc(_width*sizeof(*ncol_sums));
|
|
/*Initialize sums down each column.*/
|
|
for(x=0;x<_width;x++){
|
|
if(!_mask[x]){
|
|
g=_img[x];
|
|
col_sums[x]=(g<<logwindh-1)+g;
|
|
ncol_sums[x]=(1<<logwindh-1)+1;
|
|
}
|
|
else col_sums[x]=ncol_sums[x]=0;
|
|
}
|
|
for(y=1;y<(windh>>1);y++){
|
|
y1offs=QR_MINI(y,_height-1)*_width;
|
|
for(x=0;x<_width;x++)if(!_mask[y1offs+x]){
|
|
col_sums[x]+=_img[y1offs+x];
|
|
ncol_sums[x]++;
|
|
}
|
|
}
|
|
for(y=0;y<_height;y++){
|
|
unsigned n;
|
|
unsigned m;
|
|
int x0;
|
|
int x1;
|
|
/*Initialize the sums over the window.*/
|
|
m=(col_sums[0]<<logwindw-1)+col_sums[0];
|
|
n=(ncol_sums[0]<<logwindw-1)+ncol_sums[0];
|
|
for(x=1;x<(windw>>1);x++){
|
|
x1=QR_MINI(x,_width-1);
|
|
m+=col_sums[x1];
|
|
n+=ncol_sums[x1];
|
|
}
|
|
for(x=0;x<_width;x++){
|
|
if(!_mask[y*_width+x])g=_img[y*_width+x];
|
|
else{
|
|
g=n>0?((m<<1)+n)/(n<<1):b;
|
|
delta+=(int)g-_img[y*_width+x];
|
|
ndelta++;
|
|
}
|
|
_dst[y*_width+x]=(unsigned char)g;
|
|
/*Update the window sums.*/
|
|
if(x+1<_width){
|
|
x0=QR_MAXI(0,x-(windw>>1));
|
|
x1=QR_MINI(x+(windw>>1),_width-1);
|
|
m+=col_sums[x1]-col_sums[x0];
|
|
n+=ncol_sums[x1]-ncol_sums[x0];
|
|
}
|
|
}
|
|
/*Update the column sums.*/
|
|
if(y+1<_height){
|
|
y0offs=QR_MAXI(0,y-(windh>>1))*_width;
|
|
y1offs=QR_MINI(y+(windh>>1),_height-1)*_width;
|
|
for(x=0;x<_width;x++){
|
|
if(!_mask[y0offs+x]){
|
|
col_sums[x]-=_img[y0offs+x];
|
|
ncol_sums[x]--;
|
|
}
|
|
if(!_mask[y1offs+x]){
|
|
col_sums[x]+=_img[y1offs+x];
|
|
ncol_sums[x]++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
free(ncol_sums);
|
|
free(col_sums);
|
|
}
|
|
*_delta=delta;
|
|
*_ndelta=ndelta;
|
|
}
|
|
|
|
/*Parameters of the logistic sigmoid function that defines the threshold based
|
|
on the background intensity.
|
|
They should all be between 0 and 1.*/
|
|
#define QR_GATOS_Q (0.7)
|
|
#define QR_GATOS_P1 (0.5)
|
|
#define QR_GATOS_P2 (0.8)
|
|
|
|
/*Compute the final binarization mask according to Gatos et al.'s
|
|
method~\cite{GPP06}.*/
|
|
static void qr_gatos_mask(unsigned char *_mask,const unsigned char *_img,
|
|
const unsigned char *_background,int _width,int _height,
|
|
unsigned _b,int _nb,int _delta,int _ndelta){
|
|
unsigned thresh[256];
|
|
unsigned g;
|
|
double delta;
|
|
double b;
|
|
int x;
|
|
int y;
|
|
/*Construct a lookup table for the thresholds.
|
|
This bit uses floating point, but doesn't need to do much calculation, so
|
|
emulation should be fine.*/
|
|
b=_nb>0?(_b+0.5)/_nb:0xFF;
|
|
delta=_ndelta>0?(_delta+0.5)/_ndelta:0xFF;
|
|
for(g=0;g<256;g++){
|
|
double d;
|
|
d=QR_GATOS_Q*delta*(QR_GATOS_P2+(1-QR_GATOS_P2)/
|
|
(1+exp(2*(1+QR_GATOS_P1)/(1-QR_GATOS_P1)-4*g/(b*(1-QR_GATOS_P1)))));
|
|
if(d<1)d=1;
|
|
else if(d>0xFF)d=0xFF;
|
|
thresh[g]=(unsigned)floor(d);
|
|
}
|
|
/*Apply the adaptive threshold.*/
|
|
for(y=0;y<_height;y++)for(x=0;x<_width;x++){
|
|
g=_background[y*_width+x];
|
|
/*_background[y*_width+x]=thresh[g];*/
|
|
_mask[y*_width+x]=(unsigned char)(-(g-_img[y*_width+x]>thresh[g])&0xFF);
|
|
}
|
|
/*{
|
|
FILE *fout;
|
|
fout=fopen("thresh.png","wb");
|
|
image_write_png(_background,_width,_height,fout);
|
|
fclose(fout);
|
|
}*/
|
|
}
|
|
|
|
/*Binarizes a grayscale image.*/
|
|
void qr_binarize(unsigned char *_img,int _width,int _height){
|
|
unsigned char *mask;
|
|
unsigned char *background;
|
|
unsigned b;
|
|
int nb;
|
|
int delta;
|
|
int ndelta;
|
|
/*qr_wiener_filter(_img,_width,_height);
|
|
{
|
|
FILE *fout;
|
|
fout=fopen("wiener.png","wb");
|
|
image_write_png(_img,_width,_height,fout);
|
|
fclose(fout);
|
|
}*/
|
|
mask=(unsigned char *)malloc(_width*_height*sizeof(*mask));
|
|
qr_sauvola_mask(mask,&b,&nb,_img,_width,_height);
|
|
/*{
|
|
FILE *fout;
|
|
fout=fopen("foreground.png","wb");
|
|
image_write_png(mask,_width,_height,fout);
|
|
fclose(fout);
|
|
}*/
|
|
background=(unsigned char *)malloc(_width*_height*sizeof(*mask));
|
|
qr_interpolate_background(background,&delta,&ndelta,
|
|
_img,mask,_width,_height,b,nb);
|
|
/*{
|
|
FILE *fout;
|
|
fout=fopen("background.png","wb");
|
|
image_write_png(background,_width,_height,fout);
|
|
fclose(fout);
|
|
}*/
|
|
qr_gatos_mask(_img,_img,background,_width,_height,b,nb,delta,ndelta);
|
|
free(background);
|
|
free(mask);
|
|
}
|
|
|
|
#else
|
|
/*The above algorithms are computationally expensive, and do not work as well
|
|
as the simple algorithm below.
|
|
Sauvola by itself does an excellent job of classifying regions outside the
|
|
QR code as background, which greatly reduces the chance of false alarms.
|
|
However, it also tends to over-shrink isolated black dots inside the code,
|
|
making them easy to miss with even slight mis-alignment.
|
|
Since the Gatos method uses Sauvola as input to its background interpolation
|
|
method, it cannot possibly mark any pixels as foreground which Sauvola
|
|
classified as background, and thus suffers from the same problem.
|
|
The following simple adaptive threshold method does not have this problem,
|
|
though it produces essentially random noise outside the QR code region.
|
|
QR codes are structured well enough that this does not seem to lead to any
|
|
actual false alarms in practice, and it allows many more codes to be
|
|
detected and decoded successfully than the Sauvola or Gatos binarization
|
|
methods.*/
|
|
|
|
/*A simplified adaptive thresholder.
|
|
This compares the current pixel value to the mean value of a (large) window
|
|
surrounding it.*/
|
|
|
|
|
|
|
|
#if 0
|
|
unsigned char psram_bin[640*480]__attribute__ ((section(".psram.src")));
|
|
unsigned char *qr_binarize(const unsigned char *_img,int _width,int _height){
|
|
unsigned char *mask = NULL;
|
|
if(_width>0&&_height>0){
|
|
unsigned *col_sums;
|
|
int logwindw;
|
|
int logwindh;
|
|
int windw;
|
|
int windh;
|
|
int y0offs;
|
|
int y1offs;
|
|
unsigned g;
|
|
int x;
|
|
int y;
|
|
|
|
//mask=(unsigned char *)malloc(_width*_height*sizeof(*mask));
|
|
mask = psram_bin;
|
|
/*We keep the window size fairly large to ensure it doesn't fit completely
|
|
inside the center of a finder pattern of a version 1 QR code at full
|
|
resolution.*/
|
|
for(logwindw=4;logwindw<8&&(1<<logwindw)<(_width+7>>3);logwindw++);
|
|
for(logwindh=4;logwindh<8&&(1<<logwindh)<(_height+7>>3);logwindh++);
|
|
windw=1<<logwindw;
|
|
windh=1<<logwindh;
|
|
col_sums=(unsigned *)malloc(_width*sizeof(*col_sums));
|
|
/*Initialize sums down each column.*/
|
|
for(x=0;x<_width;x++){
|
|
g=_img[x];
|
|
col_sums[x]=(g<<logwindh-1)+g;
|
|
}
|
|
for(y=1;y<(windh>>1);y++){
|
|
y1offs=QR_MINI(y,_height-1)*_width;
|
|
for(x=0;x<_width;x++){
|
|
g=_img[y1offs+x];
|
|
col_sums[x]+=g;
|
|
}
|
|
}
|
|
|
|
for(y=0;y<_height;y++){
|
|
unsigned m;
|
|
int x0;
|
|
int x1;
|
|
/*Initialize the sum over the window.*/
|
|
m=(col_sums[0]<<logwindw-1)+col_sums[0];
|
|
for(x=1;x<(windw>>1);x++){
|
|
x1=QR_MINI(x,_width-1);
|
|
m+=col_sums[x1];
|
|
}
|
|
|
|
for(x=0;x<_width;x++){
|
|
/*Perform the test against the threshold T = (m/n)-D,
|
|
where n=windw*windh and D=3.*/
|
|
g=_img[y*_width+x];
|
|
|
|
mask[y*_width+x]=-(g+3<<logwindw+logwindh<m)&0xFF;
|
|
//printf("g:%x m:%x logwindh:%x,logwindw:%x,_width:%x,x:%x,y:%x,mask[y*_width+x]:%x\r\n",g,m,logwindh,logwindw,_width,x,y,mask[y*_width+x]);
|
|
/*Update the window sum.*/
|
|
if(x+1<_width){
|
|
x0=QR_MAXI(0,x-(windw>>1));
|
|
x1=QR_MINI(x+(windw>>1),_width-1);
|
|
m+=col_sums[x1]-col_sums[x0];
|
|
}
|
|
}
|
|
/*Update the column sums.*/
|
|
if(y+1<_height){
|
|
y0offs=QR_MAXI(0,y-(windh>>1))*_width;
|
|
y1offs=QR_MINI(y+(windh>>1),_height-1)*_width;
|
|
for(x=0;x<_width;x++){
|
|
col_sums[x]-=_img[y0offs+x];
|
|
col_sums[x]+=_img[y1offs+x];
|
|
}
|
|
}
|
|
}
|
|
free(col_sums);
|
|
}
|
|
#if defined(QR_DEBUG)
|
|
{
|
|
FILE *fout;
|
|
fout=fopen("binary.png","wb");
|
|
image_write_png(_img,_width,_height,fout);
|
|
fclose(fout);
|
|
}
|
|
#endif
|
|
return(mask);
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
//与qr_binarize一致,只是mask的空间从外部申请然后匹配
|
|
unsigned char *qr_binarize2(unsigned char *mask,const unsigned char *_img,int _width,int _height){
|
|
if(_width>0&&_height>0){
|
|
unsigned *col_sums;
|
|
int logwindw;
|
|
int logwindh;
|
|
int windw;
|
|
int windh;
|
|
int y0offs;
|
|
int y1offs;
|
|
unsigned g;
|
|
int x;
|
|
int y;
|
|
|
|
|
|
/*We keep the window size fairly large to ensure it doesn't fit completely
|
|
inside the center of a finder pattern of a version 1 QR code at full
|
|
resolution.*/
|
|
for(logwindw=4;logwindw<8&&(1<<logwindw)<((_width+7)>>3);logwindw++);
|
|
for(logwindh=4;logwindh<8&&(1<<logwindh)<((_height+7)>>3);logwindh++);
|
|
windw=1<<logwindw;
|
|
windh=1<<logwindh;
|
|
col_sums=(unsigned *)malloc(_width*sizeof(*col_sums));
|
|
/*Initialize sums down each column.*/
|
|
for(x=0;x<_width;x++){
|
|
g=_img[x];
|
|
col_sums[x]=(g<<(logwindh-1))+g;
|
|
}
|
|
for(y=1;y<(windh>>1);y++){
|
|
y1offs=QR_MINI(y,_height-1)*_width;
|
|
for(x=0;x<_width;x++){
|
|
g=_img[y1offs+x];
|
|
col_sums[x]+=g;
|
|
}
|
|
}
|
|
|
|
for(y=0;y<_height;y++){
|
|
unsigned m;
|
|
int x0;
|
|
int x1;
|
|
/*Initialize the sum over the window.*/
|
|
m=(col_sums[0]<<(logwindw-1))+col_sums[0];
|
|
for(x=1;x<(windw>>1);x++){
|
|
x1=QR_MINI(x,_width-1);
|
|
m+=col_sums[x1];
|
|
}
|
|
|
|
for(x=0;x<_width;x++){
|
|
/*Perform the test against the threshold T = (m/n)-D,
|
|
where n=windw*windh and D=3.*/
|
|
g=_img[y*_width+x];
|
|
|
|
mask[y*_width+x]=-((g+3)<<(logwindw+logwindh)<m)&0xFF;
|
|
//printf("g:%x m:%x logwindh:%x,logwindw:%x,_width:%x,x:%x,y:%x,mask[y*_width+x]:%x\r\n",g,m,logwindh,logwindw,_width,x,y,mask[y*_width+x]);
|
|
/*Update the window sum.*/
|
|
if(x+1<_width){
|
|
x0=QR_MAXI(0,x-(windw>>1));
|
|
x1=QR_MINI(x+(windw>>1),_width-1);
|
|
m+=col_sums[x1]-col_sums[x0];
|
|
}
|
|
}
|
|
/*Update the column sums.*/
|
|
if(y+1<_height){
|
|
y0offs=QR_MAXI(0,y-(windh>>1))*_width;
|
|
y1offs=QR_MINI(y+(windh>>1),_height-1)*_width;
|
|
for(x=0;x<_width;x++){
|
|
col_sums[x]-=_img[y0offs+x];
|
|
col_sums[x]+=_img[y1offs+x];
|
|
}
|
|
}
|
|
}
|
|
free(col_sums);
|
|
}
|
|
#if defined(QR_DEBUG)
|
|
{
|
|
FILE *fout;
|
|
fout=fopen("binary.png","wb");
|
|
image_write_png(_img,_width,_height,fout);
|
|
fclose(fout);
|
|
}
|
|
#endif
|
|
return(mask);
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
#if defined(TEST_BINARIZE)
|
|
#include <stdio.h>
|
|
#include "image.c"
|
|
|
|
int main(int _argc,char **_argv){
|
|
unsigned char *img;
|
|
int width;
|
|
int height;
|
|
int x;
|
|
int y;
|
|
if(_argc<2){
|
|
fprintf(stderr,"usage: %s <image>.png\n",_argv[0]);
|
|
return EXIT_FAILURE;
|
|
}
|
|
/*width=1182;
|
|
height=1181;
|
|
img=(unsigned char *)malloc(width*height*sizeof(*img));
|
|
for(y=0;y<height;y++)for(x=0;x<width;x++){
|
|
img[y*width+x]=(unsigned char)(-((x&1)^(y&1))&0xFF);
|
|
}*/
|
|
{
|
|
FILE *fin;
|
|
fin=fopen(_argv[1],"rb");
|
|
image_read_png(&img,&width,&height,fin);
|
|
fclose(fin);
|
|
}
|
|
qr_binarize(img,width,height);
|
|
/*{
|
|
FILE *fout;
|
|
fout=fopen("binary.png","wb");
|
|
image_write_png(img,width,height,fout);
|
|
fclose(fout);
|
|
}*/
|
|
free(img);
|
|
return EXIT_SUCCESS;
|
|
}
|
|
#endif
|