mission_qrcode.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. # =================================================
  2. # =============== 二维码 - 任务管理器 ===============
  3. # =================================================
  4. import base64
  5. from PIL import Image, ImageEnhance, ImageFilter
  6. from io import BytesIO
  7. from .mission import Mission
  8. try:
  9. import zxingcpp
  10. except Exception as e:
  11. zxingcpp = None
  12. zxingcppErr = str(e)
  13. class _MissionQRCodeClass(Mission):
  14. def createImage(self, text, format="QRCode", w=0, h=0, quiet_zone=-1, ec_level=-1):
  15. """
  16. 生成二维码图片
  17. format: "Aztec","Codabar","Code128","Code39","Code93","DataBar","DataBarExpanded","DataMatrix","EAN13","EAN8","ITF","LinearCodes","MatrixCodes","MaxiCode","MicroQRCode","PDF417","QRCode","UPCA","UPCE",
  18. quiet_zone: 四周的空闲区域
  19. ec_level:纠错等级,-1 - 自动, 1- L-7% , 0 - M-15%, 3 - Q-25%, 2 - H-30%
  20. 纠错仅用于Aztec、PDF417和QRCode
  21. 返回:成功返回PIL对象,失败返回错误原因字符串
  22. """
  23. # 转整数
  24. w, h = round(w), round(h)
  25. quiet_zone, ec_level = round(quiet_zone), round(ec_level)
  26. # 生成格式对象
  27. bFormat = getattr(zxingcpp.BarcodeFormat, format, None)
  28. if not bFormat:
  29. return f"[Error] format {format} not in zxingcpp.BarcodeFormat!"
  30. try:
  31. bit = zxingcpp.write_barcode(bFormat, text, w, h, quiet_zone, ec_level)
  32. except Exception as e:
  33. return f"[Error] [{format}] {e}"
  34. try:
  35. img = Image.fromarray(bit, "L")
  36. except Exception as e:
  37. return f"[Error] Image.fromarray: {e}"
  38. return img
  39. # msnInfo: { 回调函数"onXX" , 参数"argd":{ "preprocessing.xx" } }
  40. # msnList: [ { "path", "pil", "base64" } ]
  41. # def addMissionList(self, msnInfo, msnList): # 添加任务列表
  42. # return super().addMissionList(msnInfo, msnList)
  43. def msnTask(self, msnInfo, msn): # 执行msn
  44. # 导入库失败
  45. if not zxingcpp:
  46. return {
  47. "code": 901,
  48. "data": f"【Error】无法导入二维码解析器 zxingcpp 。\n[Error] Unable to import zxingcpp.\n{zxingcppErr}",
  49. }
  50. # 读入图片
  51. try:
  52. if "pil" in msn:
  53. img = msn["pil"]
  54. elif "path" in msn:
  55. imgPath = msn["path"]
  56. img = Image.open(imgPath)
  57. elif "base64" in msn:
  58. imgBase64 = msn["base64"]
  59. img = Image.open(BytesIO(base64.b64decode(imgBase64)))
  60. except Exception as e:
  61. return {
  62. "code": 202,
  63. "data": f"【Error】图片读取失败。\n[Error] Image reading failed.\n {e}",
  64. }
  65. # 预处理
  66. if "argd" in msnInfo:
  67. try:
  68. img = self._preprocessing(img, msnInfo["argd"])
  69. except Exception as e:
  70. return {
  71. "code": 203,
  72. "data": f"【Error】图片预处理失败。\n[Error] Image preprocessing failed.\n {e}",
  73. }
  74. # 二维码解析
  75. try:
  76. codes = zxingcpp.read_barcodes(img)
  77. except Exception as e:
  78. return {
  79. "code": 204,
  80. "data": f"【Error】zxingcpp 二维码解析失败。\n[Error] zxingcpp read_barcodes failed.\n {e}",
  81. }
  82. # 结果解析
  83. try:
  84. return self._zxingcpp2dict(codes)
  85. except Exception as e:
  86. return {
  87. "code": 205,
  88. "data": f"【Error】zxingcpp 结果解析失败。\n[Error] zxingcpp resule to dict failed.\n {e}",
  89. }
  90. # 解析 zxingcpp 库的返回结果,转为字典。此函数允许发生异常。
  91. # 失败返回值: {"code":错误码, "data": "错误信息字符串"}
  92. # 成功返回值: {"code":100, "data": [每个码的数据] }
  93. # data: {"box":[包围盒], "score":1, "text": "文本"}
  94. def _zxingcpp2dict(self, codes):
  95. # 图中无码
  96. if not codes:
  97. return {
  98. "code": 101,
  99. "data": "QR code not found in the image.",
  100. }
  101. # 处理结果冲每一个二维码
  102. data = []
  103. for c in codes:
  104. if not c.valid: # 码无效
  105. continue
  106. d = {}
  107. # 方向
  108. d["orientation"] = c.orientation
  109. # 位置
  110. d["box"] = [
  111. [c.position.top_left.x, c.position.top_left.y],
  112. [c.position.top_right.x, c.position.top_right.y],
  113. [c.position.bottom_right.x, c.position.bottom_right.y],
  114. [c.position.bottom_left.x, c.position.bottom_left.y],
  115. ]
  116. d["score"] = 1 # 置信度,兼容OCR格式,无意义
  117. d["end"] = "\n" # 间隔符,兼容OCR格式,无意义
  118. # 格式
  119. d["format"] = c.format.name
  120. # 内容为文本类型
  121. if c.content_type.name == "Text":
  122. d["text"] = c.text
  123. # 内容为其它格式
  124. # TODO: 现在是通用的处理方法,将二进制内容转为纯文本或base64
  125. # 或许对于某些格式的码,有更好的转文本方式
  126. else:
  127. text = f"type: {c.content_type.name}\ndata: "
  128. try:
  129. # 尝试将 bytes 转换为纯文本字符串
  130. t = c.bytes.decode("utf-8")
  131. text += t
  132. except UnicodeDecodeError:
  133. # 如果无法直接转换为纯文本,则使用 Base64 编码输出结果
  134. t = base64.b64encode(c.bytes)
  135. text += "[Base64]\n" + t.decode("utf-8")
  136. d["text"] = text
  137. data.append(d)
  138. if data:
  139. return {
  140. "code": 100,
  141. "data": data,
  142. }
  143. else:
  144. l = len(codes)
  145. return {
  146. "code": 102,
  147. "data": f"【Error】{l}组二维码解码失败。\nFailed to decode {l} sets of QR codes.",
  148. }
  149. # 图像预处理
  150. def _preprocessing(self, img, argd):
  151. """
  152. 对图像进行预处理,包括中值滤波、锐度增强、对比度增强以及可选的灰度转换和二值化。
  153. img: PIL.Image对象,待处理的图像。
  154. argd: 参数字典,包含以下可选键值对:
  155. - "preprocessing.median_filter_size": 中值滤波器的大小,应为1~9的奇数。默认不进行。
  156. - "preprocessing.sharpness_factor": 锐度增强因子,应为0.1~10。默认不调整锐度。
  157. - "preprocessing.contrast_factor": 对比度增强因子,应为0.1~10。大于1增强对比度,小于1但大于0减少对比度,1保持原样。默认不调整对比度。
  158. - "preprocessing.grayscale": 布尔值,指定是否将图像转换为灰度图像。True为转换,False为不转换。默认为False。
  159. - "preprocessing.threshold": 二值化阈值,用于灰度图像的二值化处理。应为0到255之间的整数。只有当"preprocessing.grayscale"为True时,此参数才生效。默认不进行二值化处理。
  160. 返回:
  161. 处理后的PIL.Image对象。
  162. """
  163. # 中值滤波
  164. s = round(argd.get("preprocessing.median_filter_size", -100))
  165. if s > 0 and s % 2 == 1:
  166. img = img.filter(ImageFilter.MedianFilter(size=s))
  167. # 锐度增强
  168. f = round(argd.get("preprocessing.sharpness_factor", -100))
  169. if f > 0:
  170. img = ImageEnhance.Sharpness(img).enhance(f)
  171. # 对比度增强
  172. c = argd.get("preprocessing.contrast_factor", -100)
  173. if c > 0:
  174. img = ImageEnhance.Contrast(img).enhance(c)
  175. # 转为灰度 & 二值化
  176. if argd.get("preprocessing.grayscale", False):
  177. img = img.convert("L")
  178. t = round(argd.get("preprocessing.threshold", -100))
  179. if t > -1:
  180. img = img.point(lambda p: 255 if p > t else 0)
  181. return img
  182. MissionQRCode = _MissionQRCodeClass()
  183. """
  184. zxingcpp 返回值类型
  185. # 字节
  186. bytes <class 'bytes'>: b'testtesttesttest'
  187. # 内容类型
  188. content_type <class 'zxingcpp.ContentType'>: ContentType.Text
  189. name <class 'str'>: Text
  190. value <class 'int'>: 0
  191. Binary <class 'zxingcpp.ContentType'>: ContentType.Binary
  192. GS1 <class 'zxingcpp.ContentType'>: ContentType.GS1
  193. ISO15434 <class 'zxingcpp.ContentType'>: ContentType.ISO15434
  194. Mixed <class 'zxingcpp.ContentType'>: ContentType.Mixed
  195. Text <class 'zxingcpp.ContentType'>: ContentType.Text
  196. UnknownECI <class 'zxingcpp.ContentType'>: ContentType.UnknownECI
  197. ec_level <class 'str'>: M
  198. # 二维码格式
  199. format <class 'zxingcpp.BarcodeFormat'>: BarcodeFormat.QRCode
  200. name <class 'str'>: QRCode
  201. value <class 'int'>: 8192
  202. Aztec <class 'zxingcpp.BarcodeFormat'>: BarcodeFormat.Aztec
  203. Codabar <class 'zxingcpp.BarcodeFormat'>: BarcodeFormat.Codabar
  204. Code128 <class 'zxingcpp.BarcodeFormat'>: BarcodeFormat.Code128
  205. Code39 <class 'zxingcpp.BarcodeFormat'>: BarcodeFormat.Code39
  206. Code93 <class 'zxingcpp.BarcodeFormat'>: BarcodeFormat.Code93
  207. DataBar <class 'zxingcpp.BarcodeFormat'>: BarcodeFormat.DataBar
  208. DataBarExpanded <class 'zxingcpp.BarcodeFormat'>: BarcodeFormat.DataBarExpanded
  209. DataMatrix <class 'zxingcpp.BarcodeFormat'>: BarcodeFormat.DataMatrix
  210. EAN13 <class 'zxingcpp.BarcodeFormat'>: BarcodeFormat.EAN13
  211. EAN8 <class 'zxingcpp.BarcodeFormat'>: BarcodeFormat.EAN8
  212. ITF <class 'zxingcpp.BarcodeFormat'>: BarcodeFormat.ITF
  213. LinearCodes <class 'zxingcpp.BarcodeFormat'>: BarcodeFormat.LinearCodes
  214. MatrixCodes <class 'zxingcpp.BarcodeFormat'>: BarcodeFormat.MatrixCodes
  215. MaxiCode <class 'zxingcpp.BarcodeFormat'>: BarcodeFormat.MaxiCode
  216. MicroQRCode <class 'zxingcpp.BarcodeFormat'>: BarcodeFormat.MicroQRCode
  217. NONE <class 'zxingcpp.BarcodeFormat'>: BarcodeFormat.NONE
  218. PDF417 <class 'zxingcpp.BarcodeFormat'>: BarcodeFormat.PDF417
  219. QRCode <class 'zxingcpp.BarcodeFormat'>: BarcodeFormat.QRCode
  220. UPCA <class 'zxingcpp.BarcodeFormat'>: BarcodeFormat.UPCA
  221. UPCE <class 'zxingcpp.BarcodeFormat'>: BarcodeFormat.UPCE
  222. # 方向
  223. orientation <class 'int'>: 0
  224. # 位置
  225. position <class 'zxingcpp.Position'>: 16x16 116x16 116x116 16x116
  226. bottom_left <class 'zxingcpp.Point'>: <zxingcpp.Point object at 0x00000222AEE5C370>
  227. bottom_right <class 'zxingcpp.Point'>: <zxingcpp.Point object at 0x00000222C19D07B0>
  228. top_left <class 'zxingcpp.Point'>: <zxingcpp.Point object at 0x00000222AEE5C370>
  229. top_right <class 'zxingcpp.Point'>: <zxingcpp.Point object at 0x00000222C19D0770>
  230. symbology_identifier <class 'str'>: ]Q1
  231. text <class 'str'>: testtesttesttest
  232. valid <class 'bool'>: True
  233. """