# ================================================= # =============== 二维码 - 任务管理器 =============== # ================================================= import base64 from PIL import Image, ImageEnhance, ImageFilter from io import BytesIO from .mission import Mission try: import zxingcpp except Exception as e: zxingcpp = None zxingcppErr = str(e) class _MissionQRCodeClass(Mission): def createImage(self, text, format="QRCode", w=0, h=0, quiet_zone=-1, ec_level=-1): """ 生成二维码图片 format: "Aztec","Codabar","Code128","Code39","Code93","DataBar","DataBarExpanded","DataMatrix","EAN13","EAN8","ITF","LinearCodes","MatrixCodes","MaxiCode","MicroQRCode","PDF417","QRCode","UPCA","UPCE", quiet_zone: 四周的空闲区域 ec_level:纠错等级,-1 - 自动, 1- L-7% , 0 - M-15%, 3 - Q-25%, 2 - H-30% 纠错仅用于Aztec、PDF417和QRCode 返回:成功返回PIL对象,失败返回错误原因字符串 """ # 转整数 w, h = round(w), round(h) quiet_zone, ec_level = round(quiet_zone), round(ec_level) # 生成格式对象 bFormat = getattr(zxingcpp.BarcodeFormat, format, None) if not bFormat: return f"[Error] format {format} not in zxingcpp.BarcodeFormat!" try: bit = zxingcpp.write_barcode(bFormat, text, w, h, quiet_zone, ec_level) except Exception as e: return f"[Error] [{format}] {e}" try: img = Image.fromarray(bit, "L") except Exception as e: return f"[Error] Image.fromarray: {e}" return img # msnInfo: { 回调函数"onXX" , 参数"argd":{ "preprocessing.xx" } } # msnList: [ { "path", "pil", "base64" } ] # def addMissionList(self, msnInfo, msnList): # 添加任务列表 # return super().addMissionList(msnInfo, msnList) def msnTask(self, msnInfo, msn): # 执行msn # 导入库失败 if not zxingcpp: return { "code": 901, "data": f"【Error】无法导入二维码解析器 zxingcpp 。\n[Error] Unable to import zxingcpp.\n{zxingcppErr}", } # 读入图片 try: if "pil" in msn: img = msn["pil"] elif "path" in msn: imgPath = msn["path"] img = Image.open(imgPath) elif "base64" in msn: imgBase64 = msn["base64"] img = Image.open(BytesIO(base64.b64decode(imgBase64))) except Exception as e: return { "code": 202, "data": f"【Error】图片读取失败。\n[Error] Image reading failed.\n {e}", } # 预处理 if "argd" in msnInfo: try: img = self._preprocessing(img, msnInfo["argd"]) except Exception as e: return { "code": 203, "data": f"【Error】图片预处理失败。\n[Error] Image preprocessing failed.\n {e}", } # 二维码解析 try: codes = zxingcpp.read_barcodes(img) except Exception as e: return { "code": 204, "data": f"【Error】zxingcpp 二维码解析失败。\n[Error] zxingcpp read_barcodes failed.\n {e}", } # 结果解析 try: return self._zxingcpp2dict(codes) except Exception as e: return { "code": 205, "data": f"【Error】zxingcpp 结果解析失败。\n[Error] zxingcpp resule to dict failed.\n {e}", } # 解析 zxingcpp 库的返回结果,转为字典。此函数允许发生异常。 # 失败返回值: {"code":错误码, "data": "错误信息字符串"} # 成功返回值: {"code":100, "data": [每个码的数据] } # data: {"box":[包围盒], "score":1, "text": "文本"} def _zxingcpp2dict(self, codes): # 图中无码 if not codes: return { "code": 101, "data": "QR code not found in the image.", } # 处理结果冲每一个二维码 data = [] for c in codes: if not c.valid: # 码无效 continue d = {} # 方向 d["orientation"] = c.orientation # 位置 d["box"] = [ [c.position.top_left.x, c.position.top_left.y], [c.position.top_right.x, c.position.top_right.y], [c.position.bottom_right.x, c.position.bottom_right.y], [c.position.bottom_left.x, c.position.bottom_left.y], ] d["score"] = 1 # 置信度,兼容OCR格式,无意义 d["end"] = "\n" # 间隔符,兼容OCR格式,无意义 # 格式 d["format"] = c.format.name # 内容为文本类型 if c.content_type.name == "Text": d["text"] = c.text # 内容为其它格式 # TODO: 现在是通用的处理方法,将二进制内容转为纯文本或base64 # 或许对于某些格式的码,有更好的转文本方式 else: text = f"type: {c.content_type.name}\ndata: " try: # 尝试将 bytes 转换为纯文本字符串 t = c.bytes.decode("utf-8") text += t except UnicodeDecodeError: # 如果无法直接转换为纯文本,则使用 Base64 编码输出结果 t = base64.b64encode(c.bytes) text += "[Base64]\n" + t.decode("utf-8") d["text"] = text data.append(d) if data: return { "code": 100, "data": data, } else: l = len(codes) return { "code": 102, "data": f"【Error】{l}组二维码解码失败。\nFailed to decode {l} sets of QR codes.", } # 图像预处理 def _preprocessing(self, img, argd): """ 对图像进行预处理,包括中值滤波、锐度增强、对比度增强以及可选的灰度转换和二值化。 img: PIL.Image对象,待处理的图像。 argd: 参数字典,包含以下可选键值对: - "preprocessing.median_filter_size": 中值滤波器的大小,应为1~9的奇数。默认不进行。 - "preprocessing.sharpness_factor": 锐度增强因子,应为0.1~10。默认不调整锐度。 - "preprocessing.contrast_factor": 对比度增强因子,应为0.1~10。大于1增强对比度,小于1但大于0减少对比度,1保持原样。默认不调整对比度。 - "preprocessing.grayscale": 布尔值,指定是否将图像转换为灰度图像。True为转换,False为不转换。默认为False。 - "preprocessing.threshold": 二值化阈值,用于灰度图像的二值化处理。应为0到255之间的整数。只有当"preprocessing.grayscale"为True时,此参数才生效。默认不进行二值化处理。 返回: 处理后的PIL.Image对象。 """ # 中值滤波 s = round(argd.get("preprocessing.median_filter_size", -100)) if s > 0 and s % 2 == 1: img = img.filter(ImageFilter.MedianFilter(size=s)) # 锐度增强 f = round(argd.get("preprocessing.sharpness_factor", -100)) if f > 0: img = ImageEnhance.Sharpness(img).enhance(f) # 对比度增强 c = argd.get("preprocessing.contrast_factor", -100) if c > 0: img = ImageEnhance.Contrast(img).enhance(c) # 转为灰度 & 二值化 if argd.get("preprocessing.grayscale", False): img = img.convert("L") t = round(argd.get("preprocessing.threshold", -100)) if t > -1: img = img.point(lambda p: 255 if p > t else 0) return img MissionQRCode = _MissionQRCodeClass() """ zxingcpp 返回值类型 # 字节 bytes : b'testtesttesttest' # 内容类型 content_type : ContentType.Text name : Text value : 0 Binary : ContentType.Binary GS1 : ContentType.GS1 ISO15434 : ContentType.ISO15434 Mixed : ContentType.Mixed Text : ContentType.Text UnknownECI : ContentType.UnknownECI ec_level : M # 二维码格式 format : BarcodeFormat.QRCode name : QRCode value : 8192 Aztec : BarcodeFormat.Aztec Codabar : BarcodeFormat.Codabar Code128 : BarcodeFormat.Code128 Code39 : BarcodeFormat.Code39 Code93 : BarcodeFormat.Code93 DataBar : BarcodeFormat.DataBar DataBarExpanded : BarcodeFormat.DataBarExpanded DataMatrix : BarcodeFormat.DataMatrix EAN13 : BarcodeFormat.EAN13 EAN8 : BarcodeFormat.EAN8 ITF : BarcodeFormat.ITF LinearCodes : BarcodeFormat.LinearCodes MatrixCodes : BarcodeFormat.MatrixCodes MaxiCode : BarcodeFormat.MaxiCode MicroQRCode : BarcodeFormat.MicroQRCode NONE : BarcodeFormat.NONE PDF417 : BarcodeFormat.PDF417 QRCode : BarcodeFormat.QRCode UPCA : BarcodeFormat.UPCA UPCE : BarcodeFormat.UPCE # 方向 orientation : 0 # 位置 position : 16x16 116x16 116x116 16x116 bottom_left : bottom_right : top_left : top_right : symbology_identifier : ]Q1 text : testtesttesttest valid : True """