今天老师给了张图片,里面有很多二维码和条形码,然后说不管大家用什么办法,试试看能不能用个代码尽可能多的把里面的二维码和条形码信息读出来!脑海突然闪现出之前做过的微信自动回复过程中自动生成的二维码。但似乎没太大关系(略略略),二维码不也是一张图片嘛,那就图像识别?通过解析来提取里面的信息??那啥今天去超市买快乐肥宅茶的时候,小姐姐滴一下扫了瓶子上的条形码电脑上就获取到了快乐肥宅茶的相关信息(编号,价格....),不也正是读取了条形码的信息嘛。
首先,我是这样想的:图片其实就是一个二维数组,由无数个像素点构成,那么可以把每个像素点都读取出来,存储到一个文件当中,然后通过各种算法把它们解析成文件(这个过程就是把一堆0101字节解析出我们能看懂的字符数字等,就好像是把一种一看不懂的语言翻译成中文),然后就能获得二维码或条形码上面的信息了。重点是在解析的过程中,该如何解析呢?最快的办法,当然是赶快去看看哪个库可以用?
以下是解析代码,调用的是java中的QRCodeDecoder.jar接口
然后获取图片大小和像素点,必要时还可以将图片放大。
import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; import jp.sourceforge.qrcode.QRCodeDecoder; import jp.sourceforge.qrcode.data.QRCodeImage; import jp.sourceforge.qrcode.exception.DecodingFailedException; public class ErWeiMaJieMa { /** * 解码二维码 * @param imgPath * @return String */ public String decoderQRCode(String imgPath) { // QRCode 二维码图片的文件 File imageFile = new File(imgPath); BufferedImage bufImg = null; String decodedData = null; try { bufImg = ImageIO.read(imageFile); QRCodeDecoder decoder = new QRCodeDecoder(); decodedData = new String(decoder.decode(new J2SEImage(bufImg))); // try { // System.out.println(new String(decodedData.getBytes("gb2312"), // "gb2312")); // } catch (Exception e) { // // TODO: handle exception // } } catch (IOException e) { System.out.println("Error: " + e.getMessage()); e.printStackTrace(); } catch (DecodingFailedException dfe) { System.out.println("Error: " + dfe.getMessage()); dfe.printStackTrace(); } return decodedData; } /** * @param args the command line arguments */ public static void main(String[] args) { ErWeiMaJieMa handler = new ErWeiMaJieMa(); String imgPath = "F:\\Desktop\\2.png"; String decoderContent = handler.decoderQRCode(imgPath); System.out.println("解析结果如下:"); System.out.println(decoderContent); System.out.println("========decoder success!!!"); } class J2SEImage implements QRCodeImage { BufferedImage bufImg; public J2SEImage(BufferedImage bufImg) { this.bufImg = bufImg; } //获取宽 public int getWidth() { return bufImg.getWidth(); } //获取高 public int getHeight() { return bufImg.getHeight(); } //获取像素点 public int getPixel(int x, int y) { return bufImg.getRGB(x, y); } } }
图片:
我只截取了里面的一个二维码来解析,跑出来的结果:
其实可以打开QRCodeDecoder类中的decode()方法看,发现它也是把图片转成点来解析,最后返回一个byte类型的数组。
public byte[] decode(QRCodeImage qrCodeImage) throws DecodingFailedException { //获取调整的点 Point[] adjusts = getAdjustPoints(); Vector results = new Vector(); numTryDecode = 0; while (numTryDecode < adjusts.length) { try { DecodeResult result = decode(qrCodeImage, adjusts[numTryDecode]); if (result.isCorrectionSucceeded()) { return result.getDecodedBytes(); } else { results.addElement(result); canvas.println("Decoding succeeded but could not correct"); canvas.println("all errors. Retrying.."); } } catch (DecodingFailedException dfe) { if (dfe.getMessage().indexOf("Finder Pattern") >= 0) throw dfe; } finally { numTryDecode += 1; } } if (results.size() == 0) throw new DecodingFailedException("Give up decoding"); int minErrorIndex = -1; int minError = Integer.MAX_VALUE; for (int i = 0; i < results.size(); i++) { DecodeResult result = (DecodeResult)results.elementAt(i); if (result.getNumCorrectuionFailures() < minError) { minError = result.getNumCorrectuionFailures(); minErrorIndex = i; } } canvas.println("All trials need for correct error"); canvas.println("Reporting #" + (minErrorIndex)+" that,"); canvas.println("corrected minimum errors (" +minError + ")"); canvas.println("Decoding finished."); return ((DecodeResult)results.elementAt(minErrorIndex)).getDecodedBytes(); } Point[] getAdjustPoints() { // note that adjusts affect dependently // i.e. below means (0,0), (2,3), (3,4), (1,2), (2,1), (1,1), (-1,-1) // Point[] adjusts = {new Point(0,0), new Point(2,3), new Point(1,1), // new Point(-2,-2), new Point(1,-1), new Point(-1,0), new Point(-2,-2)}; Vector adjustPoints = new Vector(); for (int d = 0; d < 4; d++) adjustPoints.addElement(new Point(1, 1)); int lastX = 0, lastY = 0; for (int y = 0; y > -4; y--) { for (int x = 0; x > -4; x--) { if (x != y && ((x+y) % 2 == 0)) { adjustPoints.addElement(new Point(x-lastX, y-lastY)); lastX = x; lastY = y; } } } Point[] adjusts = new Point[adjustPoints.size()]; for (int i = 0; i < adjusts.length; i++) adjusts[i] = (Point)adjustPoints.elementAt(i); return adjusts; } DecodeResult decode(QRCodeImage qrCodeImage, Point adjust) throws DecodingFailedException { try { if (numTryDecode == 0) { canvas.println("Decoding started"); //将图片转为二维数组 int[][] intImage = imageToIntArray(qrCodeImage); //读取图片 imageReader = new QRCodeImageReader(); //获取二维码特征 qrCodeSymbol = imageReader.getQRCodeSymbol(intImage); } else { canvas.println("--"); canvas.println("Decoding restarted #" + (numTryDecode)); qrCodeSymbol = imageReader.getQRCodeSymbolWithAdjustedGrid(adjust); } } catch (SymbolNotFoundException e) { throw new DecodingFailedException(e.getMessage()); } canvas.println("Created QRCode symbol."); canvas.println("Reading symbol."); canvas.println("Version: " + qrCodeSymbol.getVersionReference()); canvas.println("Mask pattern: " + qrCodeSymbol.getMaskPatternRefererAsString()); // blocks contains all (data and RS) blocks in QR Code symbol int[] blocks = qrCodeSymbol.getBlocks(); canvas.println("Correcting data errors."); // now blocks turn to data blocks (corrected and extracted from original blocks) blocks = correctDataBlocks(blocks); try { byte[] decodedByteArray = getDecodedByteArray(blocks, qrCodeSymbol.getVersion(), qrCodeSymbol.getNumErrorCollectionCode()); return new DecodeResult(decodedByteArray, numLastCorrectionFailures); } catch (InvalidDataBlockException e) { canvas.println(e.getMessage()); throw new DecodingFailedException(e.getMessage()); } }将图片转为二维数组的函数:
int[][] imageToIntArray(QRCodeImage image) { int width = image.getWidth(); int height = image.getHeight(); int[][] intImage = new int[width][height]; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { intImage[x][y] = image.getPixel(x,y); } } return intImage; }获取二维码特征的函数:大致思路是通过与样式校准来得到特征的。
public QRCodeSymbol getQRCodeSymbol(int[][] image) throws SymbolNotFoundException { int longSide = (image.length < image[0].length) ? image[0].length : image.length; QRCodeImageReader.DECIMAL_POINT = 23 - QRCodeUtility.sqrt(longSide / 256); bitmap = filterImage(image); canvas.println("Drawing matrix."); canvas.drawMatrix(bitmap); canvas.println("Scanning Finder Pattern."); FinderPattern finderPattern = null; try { finderPattern = FinderPattern.findFinderPattern(bitmap); } catch (FinderPatternNotFoundException e) { canvas.println("Not found, now retrying..."); bitmap = applyCrossMaskingMedianFilter(bitmap, 5); canvas.drawMatrix(bitmap); try { finderPattern = FinderPattern.findFinderPattern(bitmap); } catch (FinderPatternNotFoundException e2) { throw new SymbolNotFoundException(e2.getMessage()); } catch (VersionInformationException e2) { throw new SymbolNotFoundException(e2.getMessage()); } } catch (VersionInformationException e) { throw new SymbolNotFoundException(e.getMessage()); } canvas.println("FinderPattern at"); String finderPatternCoordinates = finderPattern.getCenter(FinderPattern.UL).toString() + finderPattern.getCenter(FinderPattern.UR).toString() + finderPattern.getCenter(FinderPattern.DL).toString(); canvas.println(finderPatternCoordinates); int[] sincos = finderPattern.getAngle(); canvas.println("Angle*4098: Sin " + Integer.toString(sincos[0]) + " " + "Cos " + Integer.toString(sincos[1])); int version = finderPattern.getVersion(); canvas.println("Version: " + Integer.toString(version)); if (version < 1 || version > 40) throw new InvalidVersionException("Invalid version: " + version); AlignmentPattern alignmentPattern = null; try { alignmentPattern = AlignmentPattern.findAlignmentPattern(bitmap, finderPattern); } catch (AlignmentPatternNotFoundException e) { throw new SymbolNotFoundException(e.getMessage()); } int matrixLength = alignmentPattern.getCenter().length; canvas.println("AlignmentPatterns at"); for (int y = 0; y < matrixLength; y++) { String alignmentPatternCoordinates = ""; for (int x = 0; x < matrixLength; x++) { alignmentPatternCoordinates += alignmentPattern.getCenter()[x][y].toString(); } canvas.println(alignmentPatternCoordinates); } //for(int i = 0; i < 500000; i++) System.out.println(""); canvas.println("Creating sampling grid."); //[TODO] need all-purpose method //samplingGrid = getSamplingGrid2_6(finderPattern, alignmentPattern); samplingGrid = getSamplingGrid(finderPattern, alignmentPattern); canvas.println("Reading grid."); boolean[][] qRCodeMatrix = null; try { qRCodeMatrix = getQRCodeMatrix(bitmap, samplingGrid); } catch (ArrayIndexOutOfBoundsException e) { throw new SymbolNotFoundException("Sampling grid exceeded image boundary"); } //canvas.drawMatrix(qRCodeMatrix); return new QRCodeSymbol(qRCodeMatrix); }我们只需要调用几个函数就能搞定的事情,其实是因为底层帮我们实现了很多的细节与处理过程。看似简单的图像识别与人脸识别应用程序,其实如果只是单纯的获取每个像素点来处理和识别,那么上亿的图片集是很难处理的。所以对于每一张图片的识别都需要计算得到特征值。
特征值分解可以得到特征值与特征向量,特征值表示的是这个特征到底有多重要,而特征向量表示这个特征是什么。不过,特征值分解也有很多的局限,比如说变换的矩阵必须是方阵。这时就要奇异值分解上场才能搞定了。
给你一副图像,要从图像库中得到匹配的图像,怎么弄?如果是两两做像素点比较是不可能完成的任务,耗时好空间。如果用其他特征点代替也许可以,但容易漏检吧。我们必须对图像数据的协方差矩阵进行降维,所以用到了PCA。
主成分分析(PrincipalComponents Analysis。即PCA,也称为K-L变换),是图像压缩中的一种最优正交变换。PCA用于统计特征提取构成了子空间法模式识别的基础。它从图像整体代数特征出发,基于图像的总体信息进行分类识别。PCA的核心思想是利用较少数量的特征对样本进行描述以达到降低特征空间维数的目的。(思考:较少数量的特征来描述会不会让识别不准确?其实是会的,所以这里也需要一定的权衡。)
而具体如何实现PCA呢?关键是特征值及相应特征向量的求取。matlab有个eig函数,opencv也有相应的函数,QR算法也可以用来求实对称矩阵的全部特征值和特征向量。
更多关于奇异值分解、PCA理论和OR算法实现可以参考下面文章:
特征值分解、奇异值分解、PCA概念整理
机器学习中的数学(5)-强大的矩阵奇异值分解(SVD)及其应用