Load the source frame
Open the supplied frame with Pillow and verify its pixel dimensions before choosing crop coordinates.
from PIL import Image
img=Image.open('/mnt/data/69b09c25-0e8d-48fb-8826-ae9405e6e49f.png')
img.size
(1847, 788)

This page reconstructs the Python cells used to inspect and enhance the supplied night-driving frame: Pillow crops, OpenCV local-contrast experiments, route-sign pixel analysis, and a Richardson–Lucy deconvolution sweep. Failed experiments are retained because they are part of the actual worklog.
Spontaneous solution from GPT-5.6-Sol to a question about identifying the location of a highway.
Open the supplied frame with Pillow and verify its pixel dimensions before choosing crop coordinates.
from PIL import Image
img=Image.open('/mnt/data/69b09c25-0e8d-48fb-8826-ae9405e6e49f.png')
img.size
(1847, 788)

Make a quick Matplotlib crop around the overhead road sign and nearby facade to inspect the highest-value clues.
import matplotlib.pyplot as plt
img=Image.open('/mnt/data/69b09c25-0e8d-48fb-8826-ae9405e6e49f.png')
crop=img.crop((180,20,750,260))
plt.figure(figsize=(14,6)); plt.imshow(crop); plt.axis('off');
This was an interactive notebook display, so no standalone image was saved at this step.
Upscale the sign region with Lanczos resampling, then increase contrast and sharpness to make letter shapes easier to inspect.
from PIL import Image, ImageEnhance, ImageFilter
sign=img.crop((220,40,690,300))
sign_big=sign.resize((1880,1040), Image.Resampling.LANCZOS)
sign_big=ImageEnhance.Contrast(sign_big).enhance(1.5)
sign_big=ImageEnhance.Sharpness(sign_big).enhance(2)
sign_big.save('/mnt/data/sign_enh.png')

Try a different enhancement pipeline: cubic upscale, local contrast enhancement in LAB space, then an unsharp mask.
import cv2, numpy as np, matplotlib.pyplot as plt, PIL.Image as PImage
arr=cv2.imread('/mnt/data/69b09c25-0e8d-48fb-8826-ae9405e6e49f.png')
# sign region exact
r=arr[45:220, 240:680]
# upscale nearest/cubic and unsharp
up=cv2.resize(r, None, fx=5, fy=5, interpolation=cv2.INTER_CUBIC)
# CLAHE on LAB
lab=cv2.cvtColor(up,cv2.COLOR_BGR2LAB)
l,a,b=cv2.split(lab)
cl=cv2.createCLAHE(clipLimit=2.0,tileGridSize=(8,8)).apply(l)
enh=cv2.cvtColor(cv2.merge([cl,a,b]),cv2.COLOR_LAB2BGR)
gauss=cv2.GaussianBlur(enh,(0,0),2)
sharp=cv2.addWeighted(enh,1.8,gauss,-0.8,0)
cv2.imwrite('/mnt/data/sign_cv.png', sharp)
True

Crop the right-hand roadside businesses and boost contrast/sharpness to inspect logos, signage and facade geometry.
right=img.crop((960,80,1840,520))
up=right.resize((1760,880), Image.Resampling.LANCZOS)
up=ImageEnhance.Contrast(up).enhance(1.35)
up=ImageEnhance.Sharpness(up).enhance(1.7)
up.save('/mnt/data/right_enh.png')

Enlarge two small sign areas with nearest-neighbor sampling to preserve the original pixel structure instead of inventing smooth edges.
import numpy as np, PIL.Image as PImage, matplotlib.pyplot as plt
im=np.array(img)
# show tiny route boxes exactly
fig,axs=plt.subplots(1,2,figsize=(8,3))
axs[0].imshow(img.crop((260,55,380,150)).resize((480,380),Image.Resampling.NEAREST)); axs[0].axis('off')
axs[1].imshow(img.crop((495,70,605,170)).resize((440,400),Image.Resampling.NEAREST)); axs[1].axis('off')
Interactive comparison only; no separate file was written.
Use a zoomed coordinate grid to refine crop boundaries for the route shields and sign text.
img0=Image.open('/mnt/data/69b09c25-0e8d-48fb-8826-ae9405e6e49f.png')
# annotate rough grid
plt.figure(figsize=(18,8))
plt.imshow(img0)
plt.xlim(150,800); plt.ylim(320,0); plt.grid()
Interactive Matplotlib output.
Make a tighter side-by-side comparison of the likely route-number panels.
fig,axs=plt.subplots(1,2,figsize=(8,3))
axs[0].imshow(img0.crop((265,55,325,105)).resize((600,500),Image.Resampling.NEAREST)); axs[0].axis('off')
axs[1].imshow(img0.crop((440,55,485,108)).resize((450,530),Image.Resampling.NEAREST)); axs[1].axis('off')
Interactive Matplotlib output.
Create a deliberately blocky enlargement of the original sign pixels so blur structure can be judged without interpolation artifacts.
from PIL import Image, ImageEnhance, ImageFilter
img=Image.open('/mnt/data/69b09c25-0e8d-48fb-8826-ae9405e6e49f.png')
# crop sign more tightly original pixels
crop=img.crop((255,35,500,185))
# nearest upscale 6x
near=crop.resize((crop.width*6,crop.height*6), Image.Resampling.NEAREST)
near.save('/mnt/data/sign_nearest.png')
print(crop.size)
(245, 150)

Verify that scikit-image is installed before trying a deconvolution experiment.
import importlib.util, sys, os, numpy as np
print("skimage", importlib.util.find_spec("skimage"))
skimage ModuleSpec(name='skimage', loader=<_frozen_importlib_external.SourceFileLoader object at 0x7f5fd7da26a0>, origin='/opt/pyvenv/lib/python3.13/site-packages/skimage/__init__.py', submodule_search_locations=['/opt/pyvenv/lib/python3.13/site-packages/skimage'])
Sweep several horizontal blur-kernel lengths and iteration counts. This first version failed while rescaling the deconvolved intensity range.
from PIL import Image
import numpy as np, matplotlib.pyplot as plt
from skimage.restoration import richardson_lucy
from skimage import exposure
img=Image.open('/mnt/data/69b09c25-0e8d-48fb-8826-ae9405e6e49f.png').convert('RGB')
# text only
c=np.array(img.crop((325,55,455,150)).convert('L'))/255.0
results=[]
for length in [3,5,7,9,11,13]:
psf=np.zeros((1,length))
psf[0,:]=1/length
for it in [5,10,20]:
de=richardson_lucy(c, psf, num_iter=it, clip=False)
de=exposure.rescale_intensity(de, in_range=np.percentile(de,[2,98]), out_range=(0,1))
results.append((length,it,de))
fig,axs=plt.subplots(6,3,figsize=(12,14))
for ax,(l,it,de) in zip(axs.ravel(),results):
ax.imshow(de,cmap='gray',vmin=0,vmax=1)
ax.set_title(f"L{l} it{it}")
ax.axis('off')
plt.tight_layout()
plt.savefig('/mnt/data/deconv_contact.png',dpi=200)
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
Repeat the sweep but normalize the 2nd–98th percentile range manually, avoiding the previous API mismatch.
from PIL import Image
import numpy as np, matplotlib.pyplot as plt
from skimage.restoration import richardson_lucy
img=Image.open('/mnt/data/69b09c25-0e8d-48fb-8826-ae9405e6e49f.png').convert('RGB')
c=np.array(img.crop((325,55,455,150)).convert('L'), dtype=float)/255.0
results=[]
for length in [3,5,7,9,11,13]:
psf=np.ones((1,length), dtype=float)/length
for it in [5,10,20]:
de=richardson_lucy(c, psf, num_iter=it, clip=False)
lo,hi=np.percentile(de,[2,98])
de=np.clip((de-lo)/(hi-lo+1e-9),0,1)
results.append((length,it,de))
fig,axs=plt.subplots(6,3,figsize=(12,14))
for ax,(l,it,de) in zip(axs.ravel(),results):
ax.imshow(de,cmap='gray',vmin=0,vmax=1)
ax.set_title(f"L{l} it{it}")
ax.axis('off')
plt.tight_layout()
plt.savefig('/mnt/data/deconv_contact.png',dpi=160)
print('/mnt/data/deconv_contact.png')
/mnt/data/deconv_contact.png

Try a slightly wider crop with stronger contrast/sharpness to compare whether the text becomes more legible.
from PIL import Image, ImageEnhance, ImageFilter
img=Image.open('/mnt/data/69b09c25-0e8d-48fb-8826-ae9405e6e49f.png')
crop=img.crop((240,30,510,200))
crop=crop.resize((1080,680))
crop=ImageEnhance.Contrast(crop).enhance(1.8)
crop=ImageEnhance.Sharpness(crop).enhance(2.5)
path='/mnt/data/sign_zoom2.png'
crop.save(path)
path
'/mnt/data/sign_zoom2.png'

Convert the source to NumPy and draw a grid over the sign region to estimate precise text-line coordinates.
from PIL import Image
import numpy as np, matplotlib.pyplot as plt
img=np.array(Image.open('/mnt/data/69b09c25-0e8d-48fb-8826-ae9405e6e49f.png').convert('RGB'))
# approximate text line bounds from original
# display sign coordinate with grid perhaps
c=img[45:170,250:500]
plt.figure(figsize=(15,6))
plt.imshow(c)
plt.xticks(np.arange(0,251,10))
plt.yticks(np.arange(0,126,10))
plt.grid()
plt.show()
Interactive Matplotlib output.
Use the red channel as a crude white-text detector on the blue sign and plot horizontal profiles to estimate where characters begin and end.
import numpy as np, matplotlib.pyplot as plt
gray=img.mean(axis=2)
# lines orig coords
top=gray[65:87,338:430] # maybe
bot=gray[94:115,338:430]
# whiteness relative to blue background: use R channel maybe text white = high R, background blue = low R
R=img[:,:,0].astype(float)
topR=R[65:90,335:435]
botR=R[94:118,335:435]
# projection positive after subtract median baseline per column? simple max or percentile
for arr,name in [(topR,'top'),(botR,'bottom')]:
prof=np.mean(arr,axis=0)
# smooth?
plt.figure(figsize=(12,2))
plt.plot(prof)
plt.title(name)
plt.xticks(range(0,101,5))
plt.grid()
plt.show
Interactive plots; the final line intentionally references plt.show without calling it, matching the original cell.
Compare RGB statistics in the two suspected route badges and central text area to see whether any route marker carries a distinct color.
from PIL import Image
import numpy as np, colorsys, pandas as pd, math
img=np.array(Image.open('/mnt/data/69b09c25-0e8d-48fb-8826-ae9405e6e49f.png').convert('RGB'))
# approximate sign bounding box x=262..494 y=47..169
# examine badge regions
regions={
'left_badge': img[55:115, 270:330],
'right_badge': img[62:120, 425:485],
'center_text': img[55:125, 330:425],
}
for name,a in regions.items():
# mean rgb and top chromatic pixels
flat=a.reshape(-1,3)
print(name, flat.mean(0), flat.min(0), flat.max(0))
# pixel proportions where red dominance or green dominance
r,g,b=flat[:,0],flat[:,1],flat[:,2]
print("red dom",np.mean((r>g*1.15)&(r>b*1.15)),"green dom",np.mean((g>r*1.15)&(g>b*1.05)), "blue dom", np.mean((b>r*1.1)&(b>g*1.05)))
left_badge [ 83.97083333 191.10555556 230.00055556] [ 15 168 208] [180 210 249] red dom 0.0 green dom 0.0 blue dom 1.0 right_badge [ 40.72988506 173.38477011 215.58132184] [ 0 128 182] [191 214 249] red dom 0.0 green dom 0.0 blue dom 1.0 center_text [ 63.30962406 181.15804511 224.63954887] [ 0 129 185] [184 216 255] red dom 0.0 green dom 0.0 blue dom 1.0
Try isolating bright lettering from the blue background with a grayscale morphological top-hat transform, then save both grayscale and top-hat variants.
import cv2, numpy as np, matplotlib.pyplot as plt, os, PIL.Image as I
im=cv2.imread('/mnt/data/69b09c25-0e8d-48fb-8826-ae9405e6e49f.png')
# sign region
s=im[45:180,260:500]
# channels perhaps use min? Text white on blue, grayscale
gray=cv2.cvtColor(s,cv2.COLOR_BGR2GRAY)
# subtract local background via morphological opening?
# white features use top-hat
kernel=cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(15,15))
tophat=cv2.morphologyEx(gray,cv2.MORPH_TOPHAT,kernel)
# crop text lines
# save variants
outs=[]
for i,arr in enumerate([gray,tophat]):
arr2=cv2.resize(arr,None,fx=6,fy=6,interpolation=cv2.INTER_CUBIC)
if i==1:
arr2=cv2.normalize(arr2,None,0,255,cv2.NORM_MINMAX)
cv2.imwrite(f'/mnt/data/sign_proc{i}.png',arr2)
'/mnt/data/sign_proc1.png'
'/mnt/data/sign_proc1.png'


Save tight top/bottom line crops using both nearest-neighbor and cubic interpolation for side-by-side visual comparison.
import cv2, numpy as np
im=cv2.imread('/mnt/data/69b09c25-0e8d-48fb-8826-ae9405e6e49f.png')
# let's save very tight text crop without badges
# original sign around x260-500 y45-180. text top x333-423 y56-84, bottom x336-418 y86-114
for name, y1,y2 in [('top',55,86),('bottom',83,115)]:
a=im[y1:y2,325:430]
# upscale nearest and cubic
for method,label in [(cv2.INTER_NEAREST,'n'),(cv2.INTER_CUBIC,'c')]:
b=cv2.resize(a,None,fx=12,fy=12,interpolation=method)
cv2.imwrite(f'/mnt/data/{name}_{label}.png',b)




Reinitialize the Pillow image object after NumPy/OpenCV experiments and confirm dimensions again.
from PIL import Image, ImageEnhance, ImageFilter
img=Image.open('/mnt/data/69b09c25-0e8d-48fb-8826-ae9405e6e49f.png')
img.size
(1847, 788)
Produce a wider, high-resolution sign crop with moderate enhancement for manual visual comparison.
crop=img.crop((240,35,510,205)).resize((1080,680))
crop=ImageEnhance.Sharpness(crop).enhance(2)
crop=ImageEnhance.Contrast(crop).enhance(1.4)
crop.save('/mnt/data/sign_crop_big.png')

Measure rendered widths of candidate place names in the same font as a rough sanity check against the blurred text lengths.
from PIL import ImageFont
font=ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 100)
for s in ["Rzeszów","Przemyśl","București","Ploiești","Rzeszow","Przemysl"]:
print(s, font.getlength(s))
Rzeszów 431.046875 Przemyśl 450.140625 București 469.859375 Ploiești 357.65625 Rzeszow 431.046875 Przemysl 450.140625
Compute a simple mean-luminance profile across the sign to identify rows most affected by bright text, arrows and reflective elements.
import numpy as np, matplotlib.pyplot as plt, pandas as pd
arr=np.array(img)
# crop original sign
sc=arr[45:185,260:500]
# luminance?
lum=sc.mean(2)
# print rough rows/cols max averages
rows=lum.mean(1)
[(i+45, rows[i]) for i in np.argsort(rows)[-20:]]
[(72, 186.79166666666666), (99, 186.95416666666668), (81, 187.18125), (97, 187.42604166666666), (98, 187.42604166666666), (54, 187.46145833333333), (55, 187.72708333333333), (73, 187.91979166666667), (74, 188.79583333333332), (80, 189.0625), (75, 189.409375), (76, 189.92604166666666), (79, 189.94479166666666), (77, 190.36458333333334), (78, 190.37916666666666), (170, 191.69166666666666), (166, 193.653125), (167, 198.89583333333334), (169, 200.14270833333333), (168, 201.58854166666666)]
Make a tighter crop of the Nissan/adjacent dealership facade to compare branding and architecture against candidate locations.
dcrop=img.crop((1030,125,1630,400)).resize((1200,550))
dcrop=ImageEnhance.Sharpness(dcrop).enhance(2)
dcrop=ImageEnhance.Contrast(dcrop).enhance(1.3)
dcrop.save('/mnt/data/dealer_crop.png')

Calculate mean and median RGB values for small sign regions. This failed because the current image array had a channel shape incompatible with reshape(-1, 3).
import numpy as np
a=np.array(img)
# show mean RGB for regions
regions={'leftshield':(270,55,323,114),'rightshield':(420,70,473,123),'bg':(330,60,390,100)}
for name,(x1,y1,x2,y2) in regions.items():
r=a[y1:y2,x1:x2]
print(name, r.reshape(-1,3).mean(0), np.median(r.reshape(-1,3),axis=0))
ValueError: cannot reshape array of size 12508 into shape (3)
Force the Pillow image to RGB before converting to NumPy, then recompute the region statistics successfully.
a=np.array(img.convert("RGB"))
for name,(x1,y1,x2,y2) in regions.items():
r=a[y1:y2,x1:x2]
print(name, r.reshape(-1,3).mean(0), np.median(r.reshape(-1,3),axis=0))
leftshield [ 89.82795011 192.17236968 229.81771666] [ 89. 192. 230.] rightshield [ 50.23246707 178.84620862 219.26877892] [ 36. 186. 221.] bg [ 78.15541667 182.68458333 224.75791667] [ 79. 190. 228.]
All image files created or used during the analysis, embedded directly into this standalone HTML page.














