40
$\begingroup$

How to generate such form of QR code which displays an image itself but is still scan-able?

mma1 poa mma2

$\endgroup$
4
  • $\begingroup$ In case anyone is slightly interested this is possible because QR codes can support high data redundancy, thus adding an image to a QR code, e.g. unitag.io/qrcode, for marketing purposes is useful. $\endgroup$ Jul 5, 2016 at 13:03
  • $\begingroup$ yes, I know one can add an image in the center of a QRcode. It's just this version can make the image full sized~ $\endgroup$
    – Wjx
    Jul 5, 2016 at 13:12
  • 1
    $\begingroup$ An alternate approach that does not rely on abusing the pixel center reading is Russ Cox's QArt codes $\endgroup$
    – Nayuki
    Jul 5, 2016 at 19:45
  • $\begingroup$ that's great and would generate a easier recognizable result~ It's just I don't know how to code a QRcode from the beginning~ Maybe you can post another answer if you want~ :) $\endgroup$
    – Wjx
    Jul 6, 2016 at 2:18

1 Answer 1

32
$\begingroup$

This version of generation code is originally posted at wolfram community.

I guess a story-telling type post would attract more upvotes and probably give some insight about how to 'solve problems' using Mathematica, so I would go into details and try to explain not only the code but also how I figured out how to write them.

How this works?

In fact, this form of QR code is not my original idea, I came across such type of QR code on internet but failed to find its origin. So I tried to figure out the principle by myself.

Carefully observe the image, one can find out that there's something odd about this QR:

wierd behavior

The marker on the corner are three times coarser than the majority of the QR. So I initially hypothesize that the QR recognition algorithm would first average the brightness of a segment, turning it into a normal QR and then recognize it. The code I used is as follows:

Block[{img = Import["http://community.wolfram.com//c/portal/getImageAttachment?filename=mathematica1.png&userId=1340903"], dat, partitioned},
 dat = ImageData@ImagePad[Binarize[ImageResize[img, Scaled[1/3]]], -4];
 partitioned = Partition[dat, {3, 3}];
 Grid[{ImageResize[#, Dimensions@dat], BarcodeRecognize@#} & /@ 
    {Image@dat,Binarize@Image@Map[Mean@*Flatten, partitioned, {2}]}]
 ]

The result proved me wrong as the averaged version cannot be properly recognized. Then further observer the QR code, I found that there are mysterious dots even in the places which should be purely white, also the dots are a bit too structural. So I suspect that normal QR code recognition algorithm only takes the color of the center dot, so I added this to the previous code:

Map[#[[2,2]]&,partitioned,{2}]

then it worked out properly!

theory

As we've already cracked the theory, we can now generate some of our own.

How to generate?

QR code generation

First we can use BarcodeImage to generate a QR code, for example here I would use: "This is a sample QR generated by Mathematica!" as the content of the QR code:

text = "This is a sample QR generated by Mathematica!";
qrraw = BarcodeImage[text, {"QR", "H"}, 1]
BarcodeRecognize@qrraw
Image processing

Then we create a black and white image to use as background, for example here we use the wolfram wolf icon:

wolfram wolf

Import, convert to grayscale and adjust the grayscale a bit:

img=ColorConvert[Rasterize[Graphics[{
  Inset[Import["http://community.wolfram.com//c/portal/getImageAttachment?filename=wolframwolf.png&userId=1340903"],{.6,.4},Automatic,.8],
  Text[Style["WOLFRAM",Bold,14],{.5,.92}]
},PlotRange->{{0,1},{0,1}},ImageSize->3ImageDimensions@qrraw]],"Grayscale"]^.45

which returns:

B&W image

Note that in order to get enough resolution while keeping the QR code easy to scan, the dimension of the QR code is best in the range of [25,50], one can test that using ImageDimensions@img and adjust it by changing the error correction level by setting {"QR",lev} where lev can be "L", "M", "Q", or "H".

Merging

Then we should merge this two image together. Here we use the technique of dither to display grayscale image using only white and black pixels. In the process of dithering, we should notice that at center of each 9*9 pixel the value should correspond to the value in the QR image, or the QR code would be invalid. The code could be easily written out as follows:

dithering[imgdat_, qrdat_] := 
 Block[{imgdat1 = imgdat, dimx, dimy, tmp1, tmp2, f = UnitStep[# - .5] &},
  {dimx, dimy} = Dimensions@imgdat;
  Quiet@Do[
    (*Rounding*)
    tmp1 = If[Mod[{i, j}, 3] == {2, 2}, qrdat[[(i + 1)/3, (j + 1)/3]], f[imgdat1[[i, j]]]];
    tmp2 = Clip[imgdat1[[i, j]] - tmp1, {-.5, .5}];
    (*Diffuse Error*)
    imgdat1[[i, j]] = tmp1;
    imgdat1[[i, j + 1]] += 0.4375 tmp2;
    If[j != 1, imgdat1[[i + 1, j - 1]] += 0.1875 tmp2];
    imgdat1[[i + 1, j]] += 0.3125 tmp2;
    imgdat1[[i + 1, j + 1]] += 0.0625 tmp2
    , {i, dimx}, {j, dimy}];
  imgdat1
  ]

Special attention should be paid to the handling key pixels of the QR code, the error created by introducing it should not be ignored, but its influence should be limited in a range, so here a Clip in error is required, while in a traditional dithering process it would be redundant.

Apply dithering to the image and we have:

Image[ditherdat=dithering[ImageData@img, ImageData@qrraw]]

output 1

Refinement

One can see that the shape of the original image is quite well preserved and key points of the QR code are properly dealt with. Then the final step is to process the key features on the corner and edge of the QR code, which is quite trivial:

replicate = (Flatten[ConstantArray[#, {3, 3}], {{3, 1}, {4, 2}}] &);

refineqr[qrdat_] := 
 Block[{qrd = qrdat, d = Length[qrdat]},
  (*Corner*)
  (qrd[[#1 ;; 24 #1 ;; #1, #2 ;; 24 #2 ;; #2]] = replicate[qrd[[2 #1 ;; 23 #1 ;; 3 #1, 2 #2 ;; 23 #2 ;; 3 #2]]]) & @@@ {{1, 1}, {1, -1}, {-1, 1}};
  (*Edge*)
  qrd[[22 ;; d - 21, 19 ;; 21]] = Transpose[qrd[[19 ;; 21, 22 ;; d - 21]] = replicate[{Mod[Range[(d + 1)/3 - 14], 2]}]];
  qrd]

Then apply this to previously get result, we get the final result, which is scan-able:

Image[final = refineqr@ditherdat]
BarcodeRecognize@%

It's usually favourable to have a 3x zoom to the image:

Image@replicate@final

final result

A fully packed version is shown in the attachment notebook file, where:

createqr[text,img]

would generate the same result.

Further optimizations could include using machine learning to further refine the display effect. Sharper lines, Less interfering key points and more could be expected.

ENJOY~


Update

@Henrik Schachner kindly remind me that the previous QR is not that easy to scan with average QR scanning software. So I made some tiny updates to make the QR more standardized and much more easier to scan:

refineqr[qrdat_] := 
 Block[{qrd = qrdat, d = Length[qrdat], temp = Fold[ArrayPad[#1, 1, #2] &, {{{0}}, 1, 0}], p},
  p = Position[Round@ListCorrelate[temp, qrdat[[2 ;; ;; 3, 2 ;; ;; 3]], {{1, 1}, {-1, -1}}, 0, Abs@*Subtract], 0, 2];
  (*Corner*)
  (qrd[[#1 ;; 24 #1 ;; #1, #2 ;; 24 #2 ;; #2]] = replicate[qrd[[2 #1 ;; 23 #1 ;; 3 #1, 2 #2 ;; 23 #2 ;; 3 #2]]]) & @@@ {{1, 1}, {1, -1}, {-1, 1}};
  (*Edge*)
  qrd[[22 ;; d - 21, 19 ;; 21]] = Transpose[ qrd[[19 ;; 21, 22 ;; d - 21]] = replicate[{Mod[Range[(d + 1)/3 - 14], 2]}]];
  (*Special*)
  (qrd[[3 #1 - 2 ;; 3 #1 + 12, 3 #2 - 2 ;; 3 #2 + 12]] = replicate@temp) & @@@ p;
  qrd]

after this update, the QR code would look like this:

edited

After minor manual edition, it could be like:

manually edited

Maybe this would be easier to scan due to the newly add correction block on the right-down corner.

Also, I think I found a better realization using same basic design principle here.


Original Post

The original QRCode is a single piece (I don't dare put it here as it's actually a figure of Chinese pre-president ......)

The most interesting phenomenon is that the reference bar has different pixel size as others. So I wrote a brief mathematica code and find out the following:

QRCode Reader will only check the color of center points defined by the reference bar and IGNORE all other points!!!

Thus we can use pixels other than the center point to create color or graylevel effect and keep the center pixel untouched to carry the information we need.

The code is fully automated and runs at quite an acceptable speed (About a second)

The code is here:

text = "This is an interesting yet beautiful QRCode that consists of an image~";
(*text="http://www.rdfz.cn/"*)

img = ImageData@
   ColorConvert[BarcodeImage[text, {"QR", "H"}, 50], "LAB"];
d = Length@img;
tgi = ImageAdjust@
   ImageResize[(*Your image here*), Most@Dimensions@img];
color[Lmat_, A_, B_] := Map[{.8 # + .2, A, B} &, Lmat, {2}]
f[0, {_?(# >= .9 &), _, _}] := color[1 - DiskMatrix[0, 3], 0, 0]
f[1, {_?(# < .1 &), _, _}] := color[DiskMatrix[0, 3], 0, 0]
f[x_, {L_, A_, B_}] := 
 color[Partition[
   Insert[RandomSample[1 - UnitStep[Range@8 - Floor[L*10] + x]], x, 
    5], 3], A, B]
f[x_List, LAB_List] := f @@@ Thread@{x, LAB}
cg[m_] := ArrayFlatten@Map[ConstantArray[#, {3, 3}] &, m, {2}]

dat = ArrayFlatten[
   f[img[[;; , ;; , 1]], 
    ImageData[ColorConvert[tgi, "LAB"]][[;; , ;; , ;; 3]]]];
(dat[[Span[24 #1], Span[24 #2]]] = 
     cg[img[[Span[8 #1], Span[8 #2]]]]) & @@@ {{1, 1}, {1, -1}, {-1, 
    1}};
dat[[22 ;; 3 d - 21, 19 ;; 21]] = 
  Transpose[
   dat[[19 ;; 21, 22 ;; 3 d - 21]] = 
    cg[{{#, 0, 0} & /@ Mod[Range[d - 14], 2]}]];

Blur[Image[dat, ColorSpace -> "LAB", ImageSize -> Large], 1]
ColorConvert[%, "Grayscale"]

result

You need to insert the text and the original image.

Hope this Q&A can help you and inspire you~

$\endgroup$
4
  • $\begingroup$ I think maybe the origin of these qr-codes are here: vecg.cs.ucl.ac.uk/Projects/SmartGeometry/halftone_QR/… and cgv.cs.nthu.edu.tw/Projects/Recreational_Graphics/… I also wrote a colorful version of this a few years ago, you might like it ;) i.ibb.co/kyg9Lts/avatar2.png $\endgroup$ Dec 3, 2020 at 20:48
  • $\begingroup$ visualead.com also did a good job an creating colorful transparent qr-codes but they were not as successful as I was :) i.ibb.co/ZTHyxZD/avatar.png $\endgroup$ Dec 3, 2020 at 22:34
  • $\begingroup$ Could this be made bigger? e.g. Instead of a 3x3 piece representing one QR pixel could it be 5x5, 7x7, etc? I imagine the decoded algorithms would cope if the camera is sufficiently close to the code as normal each pixel in the qr code is many pixels in the camera. $\endgroup$
    – Ian Miller
    Oct 23, 2022 at 1:00
  • $\begingroup$ @IanMiller I tried that before, but due to the distortion of the camera and probably some denoising algorithm built into scanners, the resulting QR code usually cannot be properly scanned. $\endgroup$
    – Wjx
    Oct 24, 2022 at 7:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.