From 67c972f7a20c4ccba1f25187beda45b14daefc97 Mon Sep 17 00:00:00 2001 From: askillman10 Date: Mon, 22 Jul 2024 09:00:35 -0500 Subject: [PATCH 1/2] update to return zbar scanned poly in qr tuple --- qreader.py | 43 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/qreader.py b/qreader.py index bc43c58..e43bbc1 100644 --- a/qreader.py +++ b/qreader.py @@ -51,6 +51,7 @@ class DecodeQRResult: image: np.ndarray result: Decoded + cropped_bbox: np.ndarray | None = None def wrap( scale_factor: float, @@ -59,6 +60,9 @@ def wrap( blur_kernel_sizes: tuple[tuple[int, int], ...] | None, image: np.ndarray, results: typing.List[Decoded], + + cropped_bbox: np.ndarray | None = None, + ) -> list[DecodeQRResult]: return [ @@ -69,6 +73,9 @@ def wrap( blur_kernel_sizes=blur_kernel_sizes, image=image, result=result, + + cropped_bbox=cropped_bbox, + ) for result in results ] @@ -144,6 +151,7 @@ def detect( bounding box in absolute coordinates, while 'bbox_xyxyn' is the bounding box in normalized coordinates (from 0. to 1.). """ + return self.detector.detect(image=image, is_bgr=is_bgr) def decode( @@ -152,6 +160,7 @@ def decode( detection_result: dict[ str, np.ndarray | float | tuple[float | int, float | int] ], + return_zbar_poly: bool = False, ) -> str | None: """ This method decodes a single QR code on the given image, described by a detection_result. @@ -185,11 +194,31 @@ def decode( f"Double decoding failed for {self.reencode_to}. Returning utf-8 decoded string." ) + if return_zbar_poly: + polygon = decodeQRResult.result.polygon + if decodeQRResult.corrections == "cropped_bbox": + bbox_xyxy = decodeQRResult.cropped_bbox + x_offset, y_offset = bbox_xyxy[0], bbox_xyxy[1] + scale_factor = decodeQRResult.scale_factor + corrected_polygon = [ + (point.x / scale_factor + x_offset, point.y / scale_factor + y_offset) + for point in polygon + ] + return decoded_str, corrected_polygon + else: + return decoded_str, None + return decoded_str + if return_zbar_poly: + return None, None return None def detect_and_decode( - self, image: np.ndarray, return_detections: bool = False, is_bgr: bool = False + self, + image: np.ndarray, + return_detections: bool = False, + is_bgr: bool = False, + return_zbar_poly: bool = False, ) -> ( tuple[ dict[str, np.ndarray | float | tuple[float | int, float | int]], str | None @@ -214,7 +243,7 @@ def detect_and_decode( """ detections = self.detect(image=image, is_bgr=is_bgr) decoded_qrs = tuple( - self.decode(image=image, detection_result=detection) + self.decode(image=image, detection_result=detection, return_zbar_poly=return_zbar_poly) for detection in detections ) @@ -312,6 +341,7 @@ def _decode_qr_zbar( ): continue + rescaled_image = cv2.resize( src=image, dsize=None, @@ -328,7 +358,10 @@ def _decode_qr_zbar( blur_kernel_sizes=None, image=rescaled_image, results=decodedQR, + + cropped_bbox=detection_result[BBOX_XYXY] if label == "cropped_bbox" else None, ) + # For QRs with black background and white foreground, try to invert the image inverted_image = image = 255 - rescaled_image decodedQR = decodeQR(inverted_image, symbols=[ZBarSymbol.QRCODE]) @@ -340,6 +373,8 @@ def _decode_qr_zbar( blur_kernel_sizes=None, image=inverted_image, results=decodedQR, + + cropped_bbox=detection_result[BBOX_XYXY] if label == "cropped_bbox" else None, ) # If it not works, try to parse to grayscale (if it is not already) @@ -361,6 +396,8 @@ def _decode_qr_zbar( blur_kernel_sizes=((5, 5), (7, 7)), image=gray, results=decodedQR, + + cropped_bbox=detection_result[BBOX_XYXY] if label == "cropped_bbox" else None, ) if len(rescaled_image.shape) == 3: @@ -386,6 +423,8 @@ def _decode_qr_zbar( blur_kernel_sizes=((3, 3),), image=sharpened_gray, results=decodedQR, + + cropped_bbox=detection_result[BBOX_XYXY] if label == "cropped_bbox" else None, ) return [] From d9f2da56911811d067a57c0d718084ba45d097b0 Mon Sep 17 00:00:00 2001 From: askillman10 Date: Mon, 22 Jul 2024 11:52:32 -0500 Subject: [PATCH 2/2] update to correct polygon for perspective shifts --- qreader.py | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/qreader.py b/qreader.py index e43bbc1..2234157 100644 --- a/qreader.py +++ b/qreader.py @@ -52,6 +52,9 @@ class DecodeQRResult: result: Decoded cropped_bbox: np.ndarray | None = None + cropped_quad: np.ndarray | None = None + corrected_perspective_values: tuple[np.ndarray, np.ndarray] | None = None + def wrap( scale_factor: float, @@ -62,7 +65,8 @@ def wrap( results: typing.List[Decoded], cropped_bbox: np.ndarray | None = None, - + cropped_quad: np.ndarray | None = None, + corrected_perspective_values: tuple[np.ndarray, np.ndarray] | None = None ) -> list[DecodeQRResult]: return [ @@ -75,7 +79,8 @@ def wrap( result=result, cropped_bbox=cropped_bbox, - + cropped_quad=cropped_quad, + corrected_perspective_values = corrected_perspective_values, ) for result in results ] @@ -205,6 +210,16 @@ def decode( for point in polygon ] return decoded_str, corrected_polygon + elif decodeQRResult.corrections == "corrected_perspective": + padded_quad_xy, dst_pts = decodeQRResult.corrected_perspective_values + M_inv = cv2.getPerspectiveTransform(dst_pts, padded_quad_xy) + corrected_polygon = cv2.perspectiveTransform(np.array([[(point.x, point.y) for point in polygon]], dtype=np.float32), M_inv)[0] + scale_factor = decodeQRResult.scale_factor + corrected_polygon = [ + (point[0] / scale_factor, point[1] / scale_factor) for point in corrected_polygon + ] + return decoded_str, corrected_polygon + else: return decoded_str, None @@ -360,6 +375,7 @@ def _decode_qr_zbar( results=decodedQR, cropped_bbox=detection_result[BBOX_XYXY] if label == "cropped_bbox" else None, + corrected_perspective_values=(detection_result[PADDED_QUAD_XY], self.__get_dst_pts(corrected_perspective)) if label == "corrected_perspective" else None, ) # For QRs with black background and white foreground, try to invert the image @@ -375,6 +391,7 @@ def _decode_qr_zbar( results=decodedQR, cropped_bbox=detection_result[BBOX_XYXY] if label == "cropped_bbox" else None, + corrected_perspective_values=(detection_result[PADDED_QUAD_XY], self.__get_dst_pts(corrected_perspective)) if label == "corrected_perspective" else None, ) # If it not works, try to parse to grayscale (if it is not already) @@ -398,6 +415,7 @@ def _decode_qr_zbar( results=decodedQR, cropped_bbox=detection_result[BBOX_XYXY] if label == "cropped_bbox" else None, + corrected_perspective_values=(detection_result[PADDED_QUAD_XY], self.__get_dst_pts(corrected_perspective)) if label == "corrected_perspective" else None, ) if len(rescaled_image.shape) == 3: @@ -425,6 +443,7 @@ def _decode_qr_zbar( results=decodedQR, cropped_bbox=detection_result[BBOX_XYXY] if label == "cropped_bbox" else None, + corrected_perspective_values=(detection_result[PADDED_QUAD_XY], self.__get_dst_pts(corrected_perspective)) if label == "corrected_perspective" else None, ) return [] @@ -474,6 +493,12 @@ def __correct_perspective( dst_img = cv2.warpPerspective(image, M, (N, N)) return dst_img + + def __get_dst_pts(self, corrected_perspective: np.ndarray) -> np.ndarray: + N = corrected_perspective.shape[0] + return np.array( + [[0, 0], [N - 1, 0], [N - 1, N - 1], [0, N - 1]], dtype=np.float32 + ) def __threshold_and_blur_decodings( self, image: np.ndarray, blur_kernel_sizes: tuple[tuple[int, int]] = ((3, 3),)