2018-05-02 17:11:53 zilu0907 阅读数 372
  • 微信公众平台深度开发v2.0第3季——二维码、模板消息

    “微信公众平台深度开发Java版 v2.0”系列课程共有6季,使用JAVA语言,系统讲解微信公众平台订阅号、服务号官方列出的全部功能接口,包括:自定义菜单、个性化菜单(按需定制菜单)、群发消息、客服消息(有限次消息推送)、模板消息接(无限次消息推送)、微信网页开发(微信WEB开发、微信游戏)、微信JSSDK开发、用户管理、获取用户基本信息、网页授权获取用户基本信息(通过WEB得到用户信息)、二维码(临时二维码、永久二维码)、事件推送、接收普通消息、被动回复用户消息,等知识点。 以及,针对微信公众号开发的服务端架构设计方案。课程以微信公众号开发视角,讲解JAVA开发微信公众号所需的框架、第三方工具。 购买套餐还赠送经典微信开发课程——[微信公众号_独立知识点]环境搭建。该课程针对各种复杂的网络环境,讲解如何构建开发环境,已解决“没有服务器”、“没有固定IP”等开发者遇到的窘境。 课程采用独立知识点讲解,一个知识点,一组课程,真正做到“简单、高效、”以短的时间、实现的学习。更多课程信息请访问CSDN。网址:http://edu.csdn.net/lecturer/631 “微信公众平台企业号开发Java版”陆续上线。 详情 qq2326321088

    10262 人正在学习 去看看 翟东平
 二维码识别步骤说明:
    1) 用自适应阈值处理图像[2]。尝试不同的参数,放入zbar识别。
    2) 如果识别失败,检测二维码的三个定位图形[3]。
    3) 二值化,腐蚀图片,获得所有轮廓[1],记录包含定位图形最多的且与定位图形距离最近的一个轮廓。
    4) 获得最小的包含该轮廓的矩形。从原图中抠下来。
    5) 把抠下来的图片用自适应阈值处理,放入zbar识别。

 参考资料:
    [1] https://blog.csdn.net/NCHFGFB/article/details/78858265 二维码仿射变换
    [2] https://blog.csdn.net/qq_38712026/article/details/78674665 自适应阈值
    [3] http://aishack.in/tutorials/scanning-qr-codes-extracting-bits/ 二维码检测
2019-06-21 00:11:42 StrangeSir 阅读数 1096
  • 微信公众平台深度开发v2.0第3季——二维码、模板消息

    “微信公众平台深度开发Java版 v2.0”系列课程共有6季,使用JAVA语言,系统讲解微信公众平台订阅号、服务号官方列出的全部功能接口,包括:自定义菜单、个性化菜单(按需定制菜单)、群发消息、客服消息(有限次消息推送)、模板消息接(无限次消息推送)、微信网页开发(微信WEB开发、微信游戏)、微信JSSDK开发、用户管理、获取用户基本信息、网页授权获取用户基本信息(通过WEB得到用户信息)、二维码(临时二维码、永久二维码)、事件推送、接收普通消息、被动回复用户消息,等知识点。 以及,针对微信公众号开发的服务端架构设计方案。课程以微信公众号开发视角,讲解JAVA开发微信公众号所需的框架、第三方工具。 购买套餐还赠送经典微信开发课程——[微信公众号_独立知识点]环境搭建。该课程针对各种复杂的网络环境,讲解如何构建开发环境,已解决“没有服务器”、“没有固定IP”等开发者遇到的窘境。 课程采用独立知识点讲解,一个知识点,一组课程,真正做到“简单、高效、”以短的时间、实现的学习。更多课程信息请访问CSDN。网址:http://edu.csdn.net/lecturer/631 “微信公众平台企业号开发Java版”陆续上线。 详情 qq2326321088

    10262 人正在学习 去看看 翟东平

背景

继上一次使用做二维码识别,公司需要高识别率的二维码识别,但ZXING实在是太弱了,对于那种二维码占比很小、图片对比度不高的,识别率低的令人咋舌,后来引入了opencv,加上恶补了一些图像处理的基础知识,总算有一个能看的过去的识别率了(但公司最后还是决定去买现成的产品。。。被嫌弃!!!)

思路

上一篇文章说到了灰化、二值化处理图片来增加识别率,整体效果不是太明显,本次识别的主要思路是,先定位,再截取,最后再针对性的对二维码所在的那一小块图片进行常规的图像处理(例如灰化、二值化、去噪、直方图均值化。。。)

划重点

  1. 我们公司所要识别的图片全是凭证、故相对来说图片类别比较单一、整体图片干扰就那么几种,故下面的代码都是针对行的进行处理,若要使用,需要对相应参数进行调试,以便寻找最合适的值(例如处理完的图片放大的比例并不是越大越好,有一个中间值)
  2. 若想要能调试,要初步了解图像处理相关的基本知识,和opencv对应的api怎么使用(opencv官网的api写的很简单,网上的案例也都是python相关的,java的基本上很少,但好早python和java案例的方法名、参数都差不多,可以借鉴)

环境

opencv的安装和导入网上一大堆,请自行百度,下载地址:https://opencv.org/releases/

话不多说,上代码(注释写的比较详细,这里就不做过多解释)

部分关于二维码三点定位的代码参考了这位博主的博文:https://www.cnblogs.com/huacanfly/p/9908408.html

import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.imageio.ImageIO;

import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.MatOfPoint2f;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.RotatedRect;
import org.opencv.core.Size;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.CLAHE;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.QRCodeDetector;
import org.opencv.utils.Converters;

import com.google.zxing.Binarizer;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.Result;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.HybridBinarizer;

public class QRcodeDecode {

	// 定位后截取出来的二维码图片放大倍数
	private static int TIMES = 4;
	
	// 图像处理后的图片存放地址
	private static String PATH = "F:\\output\\cc.jpg";

	static {
		// 加载Opencv的dll文件
		System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
	}

	public static void main(String[] args) {
		decode("C:\\Users\\guanh\\Desktop\\vouchers");
	}

	/**
	 * @param directoryPath 要进行二维码识别的图片所在文件夹目录路径
	 */
	public static void decode(String directoryPath) {
		int sum = 0;		// 统计本次识别的总张数
		int count = 0;		// 统计识别成功的张数
		int notFound = 0;	// 记录未定位成功的图片张数
		long startTime = System.currentTimeMillis();
		// 需要进行识别的图片所在文件夹路径
		File file = new File(directoryPath);
		File[] vouchers = file.listFiles();
		QRCodeDetector detector = new QRCodeDetector();
		for (File voucher : vouchers) {
			sum++;
			/**
			 * 第一次识别,直接识别,若失败,则进行图像二维码定位处理
			 */
			String qRcode = decodeQRcode(detector, voucher.getAbsolutePath());
			if (qRcode == null || "".equals(qRcode)) {
				// 对图像进行处理,定位图像中的二维码,将其截取出来
				findQRcodeAndCut(voucher.getAbsolutePath());
				File file1 = new File(PATH);
				if (file1.exists()) {
					/**
					 * 第二次识别,若失败,则将定位后截取的二维码图片进行二值化处理再识别
					 */
					qRcode = decodeQRcode(detector, PATH);
					if (qRcode == null || "".equals(qRcode)) {
						Mat mat = Imgcodecs.imread(PATH, 1);
						// 彩色图转灰度图
						Imgproc.cvtColor(mat, mat, Imgproc.COLOR_RGB2GRAY);
						// 对图像进行平滑处理
						Imgproc.blur(mat, mat, new Size(3, 3));
						// 中值去噪
						Imgproc.medianBlur(mat, mat, 5);
						// 这里定义一个新的Mat对象,主要是为了保留原图,未下次处理做准备
						Mat mat2 = new Mat();
						// 根据OTSU算法进行二值化
						Imgproc.threshold(mat, mat2, 205, 255, Imgproc.THRESH_OTSU);
						// 生成二值化后的图像
						Imgcodecs.imwrite(PATH, mat2);
						/**
						 * 第三次识别,若失败,则将图像进行限制对比度的自适应直方图均衡化处理
						 */
						qRcode = decodeQRcode(detector, PATH);
						if (qRcode == null || "".equals(qRcode)) {
							// 限制对比度的自适应直方图均衡化
							CLAHE clahe = Imgproc.createCLAHE(2, new Size(8, 8));
							clahe.apply(mat, mat);
							Imgcodecs.imwrite(PATH, mat);
							/**
							 * 第四次识别,失败就标红打印失败的图片名称
							 */
							qRcode = decodeQRcode(detector, PATH);
							if (qRcode == null || "".equals(qRcode)) {
								System.err.println(voucher.getName());
							} else {
								System.out.println(voucher.getName() + "---4---" + qRcode);
							}
						} else {
							System.out.println(voucher.getName() + "---3---" + qRcode);
						}
					} else {
						System.out.println(voucher.getName() + "---2---" + qRcode);
					}
				} else {
					notFound++;
				}
			} else {
				System.out.println(voucher.getName() + "---1---" + qRcode);
			}
			// 每次检查处理图片时是否有生成图片,若存在,则删除,避免干扰下一次图像识别结果
			File file2 = new File(PATH);
			if (file2.exists()) {
				file2.delete();
			}
			if (qRcode != null && !"".equals(qRcode)) {
				count++;
			}
		}
		long endTime = System.currentTimeMillis();
		long time = (endTime-startTime)/1000;
		System.out.println("一共扫描" + vouchers.length + "张图片,耗时" + time + "秒,平均每张耗时:" + Math.round(100.0 * time/vouchers.length)*1.0/100 + "秒");
		System.out.println(
				"未定位到的图片数量:" + notFound + ",sun = " + sum + ",count = " + count + "识别率:" + 1.0 * count / sum + "%");
	}

	public static void findQRcodeAndCut(String filePath) {
		Mat src_gray = new Mat();
		Mat src = Imgcodecs.imread(filePath, 1);
		List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
		List<MatOfPoint> markContours = new ArrayList<MatOfPoint>();
		System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
		/** 图片太小就放大 **/
		if (src.width() * src.height() < 90000) {
			Imgproc.resize(src, src, new Size(800, 600));
		}
		// 彩色图转灰度图
		Imgproc.cvtColor(src, src_gray, Imgproc.COLOR_RGB2GRAY);
		// 对图像进行平滑处理
		Imgproc.GaussianBlur(src_gray, src_gray, new Size(3, 3), 0);
		Imgproc.Canny(src_gray, src_gray, 112, 255);

		Mat hierarchy = new Mat();
		Imgproc.findContours(src_gray, contours, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_NONE);

		for (int i = 0; i < contours.size(); i++) {
			MatOfPoint2f newMtx = new MatOfPoint2f(contours.get(i).toArray());
			RotatedRect rotRect = Imgproc.minAreaRect(newMtx);
			double w = rotRect.size.width;
			double h = rotRect.size.height;
			double rate = Math.max(w, h) / Math.min(w, h);
			/***
			 * 长短轴比小于1.3,总面积大于60
			 */
			if (rate < 1.3 && w < src_gray.cols() / 4 && h < src_gray.rows() / 4
					&& Imgproc.contourArea(contours.get(i)) > 60) {
				/***
				 * 计算层数,二维码角框有五层轮廓(有说六层),这里不计自己这一层,有4个以上子轮廓则标记这一点
				 */
				double[] ds = hierarchy.get(0, i);
				if (ds != null && ds.length > 3) {
					int count = 0;
					if (ds[3] == -1) {/** 最外层轮廓排除 */
						continue;
					}
					/***
					 * 计算所有子轮廓数量
					 */
					while ((int) ds[2] != -1) {
						++count;
						ds = hierarchy.get(0, (int) ds[2]);
					}
					if (count >= 4) {
						markContours.add(contours.get(i));
					}
				}
			}
		}

		/***
		 * 二维码有三个角轮廓,正常需要定位三个角才能确定坐标,但由于公司使用的凭证干扰因素较少,故当识别到两个点的时候也将二维码定位出来;
		 * 当识别到三个点时,根据三个点定位可以确定二维码位置和形状,根据三个点组成三角形形状最大角角度判断是不是二维码的三个角
		 * 当识别到两个点时,取两个点中间点,往四周扩散截取 当小于两个点时,直接返回
		 */
		if (markContours.size() == 0) {
			return;
		} else if (markContours.size() == 1) {
			capture(markContours.get(0), src);
		} else if (markContours.size() == 2) {
			List<MatOfPoint> threePointList = new ArrayList<>();
			threePointList.add(markContours.get(0));
			threePointList.add(markContours.get(1));
			capture(threePointList, src);
		} else {
			for (int i = 0; i < markContours.size() - 2; i++) {
				List<MatOfPoint> threePointList = new ArrayList<>();
				for (int j = i + 1; j < markContours.size() - 1; j++) {
					for (int k = j + 1; k < markContours.size(); k++) {
						threePointList.add(markContours.get(i));
						threePointList.add(markContours.get(j));
						threePointList.add(markContours.get(k));
						capture(threePointList, src, i + "-" + j + "-" + k);
						threePointList.clear();
					}
				}
			}
		}
	}

	/**
	 * 针对对比度不高的图片,只能识别到一个角的,直接以该点为中心截取
	 * 
	 * @param matOfPoint
	 * @param src
	 */
	private static void capture(MatOfPoint matOfPoint, Mat src) {
		Point centerPoint = centerCal(matOfPoint);
		int width = 200;
		Rect roiArea = new Rect((int) (centerPoint.x - width) > 0 ? (int) (centerPoint.x - width) : 0,
				(int) (centerPoint.y - width) > 0 ? (int) (centerPoint.y - width) : 0, (int) (2 * width),
				(int) (2 * width));
		// 截取二维码
		Mat dstRoi = new Mat(src, roiArea);
		// 放大图片
		Imgproc.resize(dstRoi, dstRoi, new Size(TIMES * width, TIMES * width));
		Imgcodecs.imwrite(PATH, dstRoi);
	}

	/**
	 * 当只识别到二维码的两个定位点时,根据两个点的中点进行定位
	 * 
	 * @param threePointList
	 * @param src
	 */
	private static void capture(List<MatOfPoint> threePointList, Mat src) {
		Point p1 = centerCal(threePointList.get(0));
		Point p2 = centerCal(threePointList.get(1));
		Point centerPoint = new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
		double width = Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y) + 50;
		// 设置截取规则
		Rect roiArea = new Rect((int) (centerPoint.x - width) > 0 ? (int) (centerPoint.x - width) : 0,
				(int) (centerPoint.y - width) > 0 ? (int) (centerPoint.y - width) : 0, (int) (2 * width),
				(int) (2 * width));
		// 截取二维码
		Mat dstRoi = new Mat(src, roiArea);
		// 放大图片
		Imgproc.resize(dstRoi, dstRoi, new Size(TIMES * width, TIMES * width));
		Imgcodecs.imwrite(PATH, dstRoi);
	}


	/**
	 * 对图片进行矫正,裁剪
	 * 
	 * @param contours
	 * @param src
	 * @param idx
	 */
	private static void capture(List<MatOfPoint> contours, Mat src, String idx) {
		Point[] pointthree = new Point[3];
		for (int i = 0; i < 3; i++) {
			pointthree[i] = centerCal(contours.get(i));
		}
		double[] ca = new double[2];
		double[] cb = new double[2];

		ca[0] = pointthree[1].x - pointthree[0].x;
		ca[1] = pointthree[1].y - pointthree[0].y;
		cb[0] = pointthree[2].x - pointthree[0].x;
		cb[1] = pointthree[2].y - pointthree[0].y;
		/*
		 * angle1,angle2,angle3分别对应识别到的二维码定位角的三个点所组成三角形的三个角
		 */
		double angle1 = 180 / 3.1415 * Math.acos((ca[0] * cb[0] + ca[1] * cb[1])
				/ (Math.sqrt(ca[0] * ca[0] + ca[1] * ca[1]) * Math.sqrt(cb[0] * cb[0] + cb[1] * cb[1])));
		double ccw1;
		if (ca[0] * cb[1] - ca[1] * cb[0] > 0) {
			ccw1 = 0;
		} else {
			ccw1 = 1;
		}
		ca[0] = pointthree[0].x - pointthree[1].x;
		ca[1] = pointthree[0].y - pointthree[1].y;
		cb[0] = pointthree[2].x - pointthree[1].x;
		cb[1] = pointthree[2].y - pointthree[1].y;
		double angle2 = 180 / 3.1415 * Math.acos((ca[0] * cb[0] + ca[1] * cb[1])
				/ (Math.sqrt(ca[0] * ca[0] + ca[1] * ca[1]) * Math.sqrt(cb[0] * cb[0] + cb[1] * cb[1])));
		double ccw2;
		if (ca[0] * cb[1] - ca[1] * cb[0] > 0) {
			ccw2 = 0;
		} else {
			ccw2 = 1;
		}

		ca[0] = pointthree[1].x - pointthree[2].x;
		ca[1] = pointthree[1].y - pointthree[2].y;
		cb[0] = pointthree[0].x - pointthree[2].x;
		cb[1] = pointthree[0].y - pointthree[2].y;
		double angle3 = 180 / 3.1415 * Math.acos((ca[0] * cb[0] + ca[1] * cb[1])
				/ (Math.sqrt(ca[0] * ca[0] + ca[1] * ca[1]) * Math.sqrt(cb[0] * cb[0] + cb[1] * cb[1])));
		int ccw3;
		if (ca[0] * cb[1] - ca[1] * cb[0] > 0) {
			ccw3 = 0;
		} else {
			ccw3 = 1;
		}
		if (Double.isNaN(angle1) || Double.isNaN(angle2) || Double.isNaN(angle3)) {
			return;
		}

		Point[] poly = new Point[4];
		if (angle3 > angle2 && angle3 > angle1) {
			if (ccw3 == 1) {
				poly[1] = pointthree[1];
				poly[3] = pointthree[0];
			} else {
				poly[1] = pointthree[0];
				poly[3] = pointthree[1];
			}
			poly[0] = pointthree[2];
			Point temp = new Point(pointthree[0].x + pointthree[1].x - pointthree[2].x,
					pointthree[0].y + pointthree[1].y - pointthree[2].y);
			poly[2] = temp;
		} else if (angle2 > angle1 && angle2 > angle3) {
			if (ccw2 == 1) {
				poly[1] = pointthree[0];
				poly[3] = pointthree[2];
			} else {
				poly[1] = pointthree[2];
				poly[3] = pointthree[0];
			}
			poly[0] = pointthree[1];
			Point temp = new Point(pointthree[0].x + pointthree[2].x - pointthree[1].x,
					pointthree[0].y + pointthree[2].y - pointthree[1].y);
			poly[2] = temp;
		} else if (angle1 > angle2 && angle1 > angle3) {
			if (ccw1 == 1) {
				poly[1] = pointthree[1];
				poly[3] = pointthree[2];
			} else {
				poly[1] = pointthree[2];
				poly[3] = pointthree[1];
			}
			poly[0] = pointthree[0];
			Point temp = new Point(pointthree[1].x + pointthree[2].x - pointthree[0].x,
					pointthree[1].y + pointthree[2].y - pointthree[0].y);
			poly[2] = temp;
		}

		Point[] trans = new Point[4];

		int temp = 50;
		trans[0] = new Point(0 + temp, 0 + temp);
		trans[1] = new Point(0 + temp, 100 + temp);
		trans[2] = new Point(100 + temp, 100 + temp);
		trans[3] = new Point(100 + temp, 0 + temp);

		double maxAngle = Math.max(angle3, Math.max(angle1, angle2));
		// System.out.println("maxAngle:" + maxAngle);
		// 二维码为直角,最大角过大或者过小都判断为不是二维码
		if (maxAngle < 75 || maxAngle > 115) {
			return;
		}

		Mat perspectiveMmat = Imgproc.getPerspectiveTransform(
				Converters.vector_Point_to_Mat(Arrays.asList(poly), CvType.CV_32F),
				Converters.vector_Point_to_Mat(Arrays.asList(trans), CvType.CV_32F)); // warp_mat
		Mat dst = new Mat();
		// 计算透视变换结果
		Imgproc.warpPerspective(src, dst, perspectiveMmat, src.size(), Imgproc.INTER_LINEAR);

		Rect roiArea = new Rect(0, 0, 200, 200);
		Mat dstRoi = new Mat(dst, roiArea);

		// 放大图片
		Imgproc.resize(dstRoi, dstRoi, new Size(2 * dstRoi.width(), 2 * dstRoi.height()));
		Imgcodecs.imwrite(PATH, dstRoi);
	}

	/**
	 * 将Mat转换为流,为了方便测试,代码中没有将Mat转换成流进行识别,若有需要,可以不落地文件
	 * 
	 * @param m
	 * @return
	 */
	public static BufferedImage toBufferedImage(Mat m) {
		int type = BufferedImage.TYPE_BYTE_GRAY;

		if (m.channels() > 1) {
			type = BufferedImage.TYPE_3BYTE_BGR;
		}

		int bufferSize = m.channels() * m.cols() * m.rows();
		byte[] b = new byte[bufferSize];
		m.get(0, 0, b); // get all the pixels
		BufferedImage image = new BufferedImage(m.cols(), m.rows(), type);

		final byte[] targetPixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
		System.arraycopy(b, 0, targetPixels, 0, b.length);

		return image;
	}

	/**
	 * 获取轮廓的中心坐标
	 * 
	 * @param matOfPoint
	 * @return
	 */
	private static Point centerCal(MatOfPoint matOfPoint) {
		double centerx = 0, centery = 0;
		MatOfPoint2f mat2f = new MatOfPoint2f(matOfPoint.toArray());
		RotatedRect rect = Imgproc.minAreaRect(mat2f);
		Point vertices[] = new Point[4];
		rect.points(vertices);
		centerx = ((vertices[0].x + vertices[1].x) / 2 + (vertices[2].x + vertices[3].x) / 2) / 2;
		centery = ((vertices[0].y + vertices[1].y) / 2 + (vertices[2].y + vertices[3].y) / 2) / 2;
		Point point = new Point(centerx, centery);
		return point;
	}
	
	/**
	 * 解析读取二维码
	 * 先使用ZXING二维码识别,若失败,使用OPENCV自带的二维码识别
	 * 个人测试,两者的识别率差不多,都不尽人意,但一起使用还是可以略微提高一点识别率,毕竟实现算法不一样
	 * 若还要其它的识别,类似Zbar,都可以集成进来
	 * 
	 * @param qrCodePath 二维码图片路径
	 * @return 成功返回二维码识别结果,失败返回null
	 * @throws Exception 
	 */
	public static String decodeQRcode(QRCodeDetector detector, String qrCodePath){
		String qrCodeText = null;
		try {
			BufferedImage image = ImageIO.read(new File(qrCodePath));
			LuminanceSource source = new BufferedImageLuminanceSource(image);
			Binarizer binarizer = new HybridBinarizer(source);
			BinaryBitmap binaryBitmap = new BinaryBitmap(binarizer);
			Map<DecodeHintType, Object> hints = new HashMap<DecodeHintType, Object>();
			hints.put(DecodeHintType.CHARACTER_SET, "UTF-8");
			Result result = new MultiFormatReader().decode(binaryBitmap, hints);
			qrCodeText = result.getText();
		} catch (Exception e) {
			qrCodeText = detector.detectAndDecode(Imgcodecs.imread(qrCodePath, 1));
		}
		return qrCodeText;
	}
}

总结

今天也参加了和专门做二维码识别的公司的会议,大致了解了他们的产品实现,整体思路差不多,但比不过人家专业,若公司真要求很高,还是买产品来的实在。

2009-06-16 09:11:00 cy513 阅读数 35375
  • 微信公众平台深度开发v2.0第3季——二维码、模板消息

    “微信公众平台深度开发Java版 v2.0”系列课程共有6季,使用JAVA语言,系统讲解微信公众平台订阅号、服务号官方列出的全部功能接口,包括:自定义菜单、个性化菜单(按需定制菜单)、群发消息、客服消息(有限次消息推送)、模板消息接(无限次消息推送)、微信网页开发(微信WEB开发、微信游戏)、微信JSSDK开发、用户管理、获取用户基本信息、网页授权获取用户基本信息(通过WEB得到用户信息)、二维码(临时二维码、永久二维码)、事件推送、接收普通消息、被动回复用户消息,等知识点。 以及,针对微信公众号开发的服务端架构设计方案。课程以微信公众号开发视角,讲解JAVA开发微信公众号所需的框架、第三方工具。 购买套餐还赠送经典微信开发课程——[微信公众号_独立知识点]环境搭建。该课程针对各种复杂的网络环境,讲解如何构建开发环境,已解决“没有服务器”、“没有固定IP”等开发者遇到的窘境。 课程采用独立知识点讲解,一个知识点,一组课程,真正做到“简单、高效、”以短的时间、实现的学习。更多课程信息请访问CSDN。网址:http://edu.csdn.net/lecturer/631 “微信公众平台企业号开发Java版”陆续上线。 详情 qq2326321088

    10262 人正在学习 去看看 翟东平

        欧美和日本,二维码的使用比较广泛,最近看到一则新闻,我们国家也在航空服务中使用二维码了。二维码具有信息容量大、纠错能力强、可靠性高、成本低、防伪性好、持久耐用等一维条码所不具备的优良特点。二维码的种类繁多,常用的有DM码、QR码,扫描设备可以是激光或者光学仪器。

    本项目做的是Visual码的识别系统,使用一般的摄像头(如手机)扫描输入图像(640×480),在Intel(R)Pentium(R)Dual,1.73GHz的硬件条件下运行大约用时100毫秒,在ARM920T,0.4GHz,WinCE 5.0下运行大约用时1秒。

        下图就是Visual码,

                         

 

这种二维码是11×11 的矩阵,每一位是白或黑色,表示二进制信息;三个用来定位的角点(右图蓝色),一长一短两导向条(右图紫色),长度分别为57位;与它们邻近的位均为白色以区分;剩下的83 位是信息位。

 

 

Visual码的这种结构使得它具有旋转不变性,更准确的来说是透视不变性,摄像头拍摄二维码(任意角度和距离),那么实物和图像中的二维码构成中心(镜头)投影变换,即透视变换;Visual码的这种不变性使得可以使用计算机视觉和图像处理的方法去处理获取到的包含二维码图像,从而获取二维码数据。

 

处理过程的一些演示图例:

 绿色X标记找到的数据

 

 

操作界面,右边一栏显示获取到的数据

Michael Rohs2004年开发的一个开源二维条码项目中设计出了Visual码, 目前已有二维码的设计仿效了该结构,虽然各种码制不一致,但识别方法有着共通之处。

2016-08-23 17:37:02 Jacky_Ponder 阅读数 2518
  • 微信公众平台深度开发v2.0第3季——二维码、模板消息

    “微信公众平台深度开发Java版 v2.0”系列课程共有6季,使用JAVA语言,系统讲解微信公众平台订阅号、服务号官方列出的全部功能接口,包括:自定义菜单、个性化菜单(按需定制菜单)、群发消息、客服消息(有限次消息推送)、模板消息接(无限次消息推送)、微信网页开发(微信WEB开发、微信游戏)、微信JSSDK开发、用户管理、获取用户基本信息、网页授权获取用户基本信息(通过WEB得到用户信息)、二维码(临时二维码、永久二维码)、事件推送、接收普通消息、被动回复用户消息,等知识点。 以及,针对微信公众号开发的服务端架构设计方案。课程以微信公众号开发视角,讲解JAVA开发微信公众号所需的框架、第三方工具。 购买套餐还赠送经典微信开发课程——[微信公众号_独立知识点]环境搭建。该课程针对各种复杂的网络环境,讲解如何构建开发环境,已解决“没有服务器”、“没有固定IP”等开发者遇到的窘境。 课程采用独立知识点讲解,一个知识点,一组课程,真正做到“简单、高效、”以短的时间、实现的学习。更多课程信息请访问CSDN。网址:http://edu.csdn.net/lecturer/631 “微信公众平台企业号开发Java版”陆续上线。 详情 qq2326321088

    10262 人正在学习 去看看 翟东平

1、步骤

(1) 提取图像中条码区

(2) 识别条码

2、示例

2.1、一维码的识别


图1.1原图


图1.2提取条码区


图1.3识别结果

 

2.2、二维码的识别


图2.1原图


图2.2提取条码区


图2.3识别结果


作者:Jacky_Ponder,转载或分享请注明出处。QQ:2814152689

2019-08-13 13:47:19 qinlele1994 阅读数 187
  • 微信公众平台深度开发v2.0第3季——二维码、模板消息

    “微信公众平台深度开发Java版 v2.0”系列课程共有6季,使用JAVA语言,系统讲解微信公众平台订阅号、服务号官方列出的全部功能接口,包括:自定义菜单、个性化菜单(按需定制菜单)、群发消息、客服消息(有限次消息推送)、模板消息接(无限次消息推送)、微信网页开发(微信WEB开发、微信游戏)、微信JSSDK开发、用户管理、获取用户基本信息、网页授权获取用户基本信息(通过WEB得到用户信息)、二维码(临时二维码、永久二维码)、事件推送、接收普通消息、被动回复用户消息,等知识点。 以及,针对微信公众号开发的服务端架构设计方案。课程以微信公众号开发视角,讲解JAVA开发微信公众号所需的框架、第三方工具。 购买套餐还赠送经典微信开发课程——[微信公众号_独立知识点]环境搭建。该课程针对各种复杂的网络环境,讲解如何构建开发环境,已解决“没有服务器”、“没有固定IP”等开发者遇到的窘境。 课程采用独立知识点讲解,一个知识点,一组课程,真正做到“简单、高效、”以短的时间、实现的学习。更多课程信息请访问CSDN。网址:http://edu.csdn.net/lecturer/631 “微信公众平台企业号开发Java版”陆续上线。 详情 qq2326321088

    10262 人正在学习 去看看 翟东平

代码实现简单环境下的条形码与二维码的定位与识别:

原图:

#include<iostream>
#include<opencv2\opencv.hpp>
#include<zbar.h>

using namespace std;
using namespace cv;
using namespace zbar;

//寻找最大的轮廓
static vector<cv::Point> FindBiggestContour(Mat src)
{
	int imax = 0; //代表最大轮廓的序号
	int imaxcontour = -1; //代表最大轮廓的大小
	std::vector<std::vector<cv::Point>>contours;
	findContours(src, contours, RETR_LIST, CHAIN_APPROX_SIMPLE);
	for (int i = 0; i<contours.size(); i++)
	{
		int itmp = contourArea(contours[i]);//这里采用的是轮廓大小
		if (imaxcontour < itmp)
		{
			imax = i;
			imaxcontour = itmp;
		}
	}
	return contours[imax];
}

//获取二维码
void detect_decode_qrcode()
{
	Mat img = imread("barcode_qrcode.jpg");
	if (img.empty())
	{
		cout << "reading images fails" << endl;
	}
	//灰度化
	Mat img_gray, img_bin;
	cvtColor(img, img_gray, COLOR_BGR2GRAY);
	threshold(img_gray, img_bin, 100, 255, THRESH_OTSU | THRESH_BINARY_INV);  //THRESH_BINARY_INV  二值化取反						 
	vector<vector<Point>> contours, contours2; //找轮廓  找到的轮廓按照树的方式排列
	vector<Vec4i> hierarchy;
	findContours(img_bin, contours, hierarchy, RETR_TREE, CHAIN_APPROX_NONE);
	int c = 0, ic = 0, area = 0;
	int parentIdx = -1;
	for (int i = 0; i < contours.size(); i++)
	{											//遍历所有的大轮廓									
		if (hierarchy[i][2] != -1 && ic == 0)  //如果 这个大轮廓没有父轮廓 hierarchy[i][2] != -1 说明他是存在子轮廓的
		{
			parentIdx = i;
			ic++;
		}
		else if (hierarchy[i][2] != -1)
		{
			ic++;
		}
		//最外面的清0
		else if (hierarchy[i][2] == -1)
		{
			ic = 0;
			parentIdx = -1;
		}
		//找到定位点信息
		if (ic == 2)
		{
			contours2.push_back(contours[parentIdx]);
			ic = 0;
			parentIdx = -1;
		}
	}
	//二维码中间是应该有三个特征轮廓的,如果等于3 那么就认为它是有二维码的
	if (contours2.size() != 3)
	{
		printf("finding 3 rects fails \n");
	}
	//把二维码最外面的轮廓构造成一个新的点集
	Rect new_rect;
	vector<Point> all_points;
	for (int i = 0; i < contours2.size(); i++)
	{
		for (int j = 0; j < contours2[i].size(); j++)
			all_points.push_back(contours2[i][j]);
	}
	new_rect = boundingRect(all_points);  //根据二维码构成得点集,找到一个最小的外包所有点集 的矩形
										  //  Rect rect(230, 5, 280, 290);//左上坐标(x,y)和矩形的长(x)宽(y)
										  //  cv::rectangle(src, rect, Scalar(255, 0, 0),1, LINE_8,0);
	rectangle(img, new_rect, Scalar(0, 0, 255), 8, 0);
	Mat result_img = img_gray(new_rect);   //将找到的矩形 放进灰度图中,这样图片就可以根据矩形切割出来了
	ImageScanner scanner;
	scanner.set_config(ZBAR_QRCODE, ZBAR_CFG_ENABLE, 1);
	int width = result_img.step;  //因为这一小部分是截取出来的
	int height = result_img.rows;
	uchar *raw = (uchar *)result_img.data;
	Image imageZbar(width, height, "Y800", raw, width * height);
	scanner.scan(imageZbar);
	Image::SymbolIterator symbol = imageZbar.symbol_begin();
	if (imageZbar.symbol_begin() == imageZbar.symbol_end())
	{
		cout << "查询二维码失败,请检查图片!" << endl;
	}
	for (; symbol != imageZbar.symbol_end(); ++symbol)
	{
		cout << "类型:" << endl << symbol->get_type_name() << endl << endl;
		cout << "二维码:" << endl << symbol->get_data() << endl << endl;
	}
	imageZbar.set_data(NULL, 0);
	//imshow("mat",img);
	imshow("mat1", result_img);
}


//获取条形码
void detect_decode_barcode()
{
	Mat src = imread("barcode_qrcode.jpg");
	if (src.empty())
	{
		cout << "reading images fails" << endl;
	}
	Mat sobel;
	Mat canny;
	Mat canny_output;
	//1、//灰度化
	Mat gray;
	cvtColor(src, gray, COLOR_BGR2GRAY); 
	//2、二值化
	//Canny(gray, canny, 100, 255);
	Mat bin;
	threshold(gray, bin, 0, 255, THRESH_OTSU | THRESH_BINARY_INV);
	//3、形态学滤波
	Mat element = getStructuringElement(MORPH_ELLIPSE, Size(12, 2));
	morphologyEx(bin, bin, MORPH_DILATE, element); //形态学滤波  找到条形码 大白
	//morphologyEx(canny, canny, MORPH_ERODE, element);
	//4、寻找最大的轮廓
	Rect boundRect = boundingRect(Mat(FindBiggestContour(bin)));
	Mat result_img = gray(boundRect);
	imshow("mat2", result_img);

	ImageScanner scanner;
	scanner.set_config(ZBAR_NONE, ZBAR_CFG_ENABLE, 1);
	int width = result_img.step;
	int height = result_img.rows;
	uchar *raw = (uchar *)result_img.data;
	Image imageZbar(width, height, "Y800", raw, width * height);
	scanner.scan(imageZbar); //扫描条码      
	Image::SymbolIterator symbol = imageZbar.symbol_begin();
	if (imageZbar.symbol_begin() == imageZbar.symbol_end())
	{
		cout << "查询条码失败,请检查图片!" << endl;
	}
	for (; symbol != imageZbar.symbol_end(); ++symbol)
	{
		cout << "类型:" << endl << symbol->get_type_name() << endl << endl;
		cout << "条码:" << endl << symbol->get_data() << endl << endl;
	}
	imageZbar.set_data(NULL, 0);
}
//主函数
int main()
{
	detect_decode_qrcode(); 
	detect_decode_barcode();
	waitKey(0);
	return 0;
}

 

 

检测结果图:

调用Zbar库函数,识别分割出来的二维码与条形码

没有更多推荐了,返回首页