33from pathlib import Path
44
55import cv2
6+ import numpy as np
67
78from dynamsoft_barcode_reader_bundle import CaptureVisionRouter , EnumPresetTemplate
89
@@ -29,6 +30,15 @@ def parse_args():
2930 action = "store_true" ,
3031 help = "Print the decode result as JSON." ,
3132 )
33+ parser .add_argument (
34+ "--fallback-profile" ,
35+ choices = ["none" , "context-retry" ],
36+ default = "none" ,
37+ help = (
38+ "Retry a small prioritized set of app-level image variants when the first decode fails. "
39+ "'context-retry' favors context expansion and simple resampling/binarization retries."
40+ ),
41+ )
3242 return parser .parse_args ()
3343
3444
@@ -48,18 +58,59 @@ def resolve_template_name(template_file, cli_template_name):
4858 return templates [0 ]["Name" ]
4959
5060
51- def decode (image_path , template_file = None , template_name = None ):
52- ensure_dbr_license ()
53- router = CaptureVisionRouter ()
54- if template_file :
55- err , msg = router .init_settings_from_file (str (template_file ))
56- if err != 0 :
57- print (f"[DBR] Template load failed ({ err } ): { msg } " )
61+ def preprocess_image (cv_img , pad ):
62+ return cv2 .copyMakeBorder (
63+ cv_img ,
64+ pad ,
65+ pad ,
66+ pad ,
67+ pad ,
68+ cv2 .BORDER_CONSTANT ,
69+ value = (255 , 255 , 255 ),
70+ )
5871
59- cv_img = cv2 .imread (str (image_path ))
60- if cv_img is None :
61- raise RuntimeError (f"Failed to load image with OpenCV: { image_path } " )
6272
73+ def build_fallback_attempts (cv_img , fallback_profile = "none" ):
74+ attempts = [("original" , cv_img )]
75+ if fallback_profile == "none" :
76+ return attempts
77+
78+ def add_attempt (name , image ):
79+ for existing_name , _ in attempts :
80+ if existing_name == name :
81+ return
82+ attempts .append ((name , image ))
83+
84+ for extra_pad in (16 , 24 , 40 ):
85+ add_attempt (f"padded_{ extra_pad } " , preprocess_image (cv_img , extra_pad ))
86+
87+ base = preprocess_image (cv_img , 16 )
88+ nearest_2x = cv2 .resize (base , None , fx = 2 , fy = 2 , interpolation = cv2 .INTER_NEAREST )
89+ add_attempt ("nearest_2x" , nearest_2x )
90+
91+ nearest_4x = cv2 .resize (base , None , fx = 4 , fy = 4 , interpolation = cv2 .INTER_NEAREST )
92+ add_attempt ("nearest_4x" , nearest_4x )
93+
94+ gray4 = cv2 .cvtColor (nearest_4x , cv2 .COLOR_BGR2GRAY )
95+ otsu = cv2 .threshold (gray4 , 0 , 255 , cv2 .THRESH_BINARY + cv2 .THRESH_OTSU )[1 ]
96+ add_attempt ("nearest_4x_otsu" , otsu )
97+ add_attempt ("nearest_4x_otsu_inverted" , cv2 .bitwise_not (otsu ))
98+
99+ adaptive = cv2 .adaptiveThreshold (
100+ gray4 ,
101+ 255 ,
102+ cv2 .ADAPTIVE_THRESH_GAUSSIAN_C ,
103+ cv2 .THRESH_BINARY ,
104+ 31 ,
105+ 2 ,
106+ )
107+ add_attempt ("nearest_4x_adaptive" , adaptive )
108+ add_attempt ("nearest_4x_adaptive_inverted" , cv2 .bitwise_not (adaptive ))
109+
110+ return attempts
111+
112+
113+ def decode_with_router (router , cv_img , template_name ):
63114 result = router .capture (cv_img , template_name )
64115
65116 items = []
@@ -81,6 +132,36 @@ def decode(image_path, template_file=None, template_name=None):
81132 return items
82133
83134
135+ def decode (image_path , template_file = None , template_name = None , fallback_profile = "none" ):
136+ ensure_dbr_license ()
137+ router = CaptureVisionRouter ()
138+ if template_file :
139+ err , msg = router .init_settings_from_file (str (template_file ))
140+ if err != 0 :
141+ print (f"[DBR] Template load failed ({ err } ): { msg } " )
142+
143+ cv_img = cv2 .imread (str (image_path ))
144+ if cv_img is None :
145+ raise RuntimeError (f"Failed to load image with OpenCV: { image_path } " )
146+
147+ attempts = build_fallback_attempts (cv_img , fallback_profile )
148+ best_items = []
149+ best_attempt = attempts [0 ][0 ]
150+
151+ for attempt_name , attempt_image in attempts :
152+ if isinstance (attempt_image , np .ndarray ) and attempt_image .ndim == 2 :
153+ attempt_image = cv2 .cvtColor (attempt_image , cv2 .COLOR_GRAY2BGR )
154+
155+ items = decode_with_router (router , attempt_image , template_name )
156+ if items :
157+ return items , attempt_name
158+
159+ if not best_items :
160+ best_attempt = attempt_name
161+
162+ return best_items , best_attempt
163+
164+
84165def main ():
85166 args = parse_args ()
86167 image_path = Path (args .image ).resolve ()
@@ -94,7 +175,12 @@ def main():
94175 raise SystemExit (f"Template not found: { template_file } " )
95176
96177 template_name = resolve_template_name (template_file , args .template_name )
97- items = decode (image_path , template_file , template_name )
178+ items , attempt_name = decode (
179+ image_path ,
180+ template_file ,
181+ template_name ,
182+ args .fallback_profile ,
183+ )
98184
99185 if args .json :
100186 print (
@@ -103,6 +189,8 @@ def main():
103189 "image" : str (image_path ),
104190 "template_file" : str (template_file ) if template_file else None ,
105191 "template_name" : template_name ,
192+ "fallback_profile" : args .fallback_profile ,
193+ "attempt" : attempt_name ,
106194 "items" : items ,
107195 },
108196 indent = 2 ,
@@ -113,6 +201,8 @@ def main():
113201 print (f"image: { image_path } " )
114202 print (f"template_file: { template_file if template_file else 'builtin' } " )
115203 print (f"template_name: { template_name } " )
204+ print (f"fallback_profile: { args .fallback_profile } " )
205+ print (f"attempt: { attempt_name } " )
116206 if not items :
117207 print ("NO_RESULT" )
118208 return
0 commit comments