Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
1d2b88e
auto scale QRcode
alexandrecormierDT Jun 5, 2024
58b59b4
complete image manipulation
alexandrecormierDT Jul 10, 2024
1ef801b
chained image modification and qrcode placement
alexandrecormierDT Jul 10, 2024
f6da47c
qrcode working again
alexandrecormierDT Jul 17, 2024
8d7884d
black and withe filter
alexandrecormierDT Jul 17, 2024
c777170
vertical and horizontal combine
alexandrecormierDT Jul 18, 2024
8a182e2
maximise
alexandrecormierDT Jul 22, 2024
86a9366
Update Harmoniser.py
alexandrecormierDT Jul 22, 2024
824e045
hide qrcodes in image
alexandrecormierDT Sep 27, 2024
0952493
working script
alexandrecormierDT Oct 1, 2024
bbe22e1
better qrcode reading with negative image
alexandrecormierDT Oct 1, 2024
d7238e1
code ready for production
alexandrecormierDT Oct 2, 2024
b34367f
Update main.py
alexandrecormierDT Oct 2, 2024
9d65bd8
Update main.py
alexandrecormierDT Oct 2, 2024
1ba815f
dont use cv2 for the moment
alexandrecormierDT Oct 2, 2024
3091f37
Update QRIntegrator.py
alexandrecormierDT Oct 2, 2024
fcf3b77
video detection and more robust integration
alexandrecormierDT Oct 3, 2024
24ab0bc
Update PathManager.py
alexandrecormierDT Oct 4, 2024
e9f124d
select non similar video frames for testing
alexandrecormierDT Oct 4, 2024
1502cf2
add search list for sgreasuest
alexandrecormierDT Oct 9, 2024
5ad8bf4
Update ImageStamp.py
alexandrecormierDT Oct 9, 2024
678bf6c
watermark
alexandrecormierDT Nov 6, 2024
6809a9b
if no output provided just overwrite the input
alexandrecormierDT Nov 8, 2024
aa5848f
watermark , keep the image size
alexandrecormierDT Nov 8, 2024
52a5ffb
combiner , resize the images if the final width is over 15000
alexandrecormierDT Nov 25, 2024
b7aad7b
Update Combiner.py
alexandrecormierDT Nov 25, 2024
85123c1
don't stop the batch if there is a corropted image
alexandrecormierDT Dec 10, 2024
c84765d
Update ImageFilter.py
alexandrecormierDT Dec 10, 2024
e3ab71f
Update ImageFilter.py
alexandrecormierDT Dec 10, 2024
86c634b
blind fix try catch on color bug
alexandrecormierDT Feb 7, 2025
7d67f9f
diff map available
alexandrecormierDT Mar 18, 2025
15225e5
Update ImageFilter.py
alexandrecormierDT Mar 18, 2025
c72a263
if image deos not exist do not stop
alexandrecormierDT Mar 18, 2025
407640a
fail louder if magic string is bad
alexandrecormierDT Nov 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,5 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
/test/output
/test/output
1 change: 1 addition & 0 deletions imagestamp.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
python %~dp0/src/main.py %*
File renamed without changes.
215 changes: 215 additions & 0 deletions src/classes/BlobDetector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
import cv2
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
import math

class BlobDetector:

def __init__(self) -> None:

pass

def _get_contrast_points(self,_image_path)->list:
# read image
img = cv2.imread(_image_path)

# convert img to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# do adaptive threshold on gray image
thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 101, 3)

# apply morphology open then close
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))
blob = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (9,9))
blob = cv2.morphologyEx(blob, cv2.MORPH_CLOSE, kernel)


cv2.imwrite("doco3_contour.jpg", blob)
cv2.imshow("IMAGE", blob)
cv2.waitKey(0)


image = Image.open("doco3_contour.jpg")

def _unsgined(self,_int:int)->int:
if _int < 0:
return _int*-1
return _int


def _square_color_variation_is_bellow_thres(self,_image:cv2.typing.MatLike,_top_x:int,_top_y:int,_width:int,_thres:int):



def _pixels_color_variation_is_bellow_thres(self,_image:cv2.typing.MatLike,_pixels:list[list],_start_value:int=0,_thres:int=100)->bool:
last_value = _start_value
for pix in _pixels:
value = _image[pix["y"],pix["x"]]
value_distance = self._unsgined(last_value -value )
if value_distance > _thres:
return False
last_value = value
return True


def _detect_contrast_points(self,_image_path:str,_width)->list:

image = cv2.imread(_image_path)

# grab the image dimensions
h = image.shape[0]
w = image.shape[1]

# convert img to grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# do adaptive threshold on gray image
thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 101, 3)

#cv2.imshow("IMAGE", thresh)
#cv2.waitKey(0)

last_h_value = 0
thres = 200
points = []
aproximation = 1
last_x_value = 0
last_y_value = 0
x_distance = 0

max_points = 300

# loop over the image, pixel by pixel

padding = 5

final_with = _width+padding

for y in range(0, h):
if y % final_with != 0:
continue
for x in range(0, w):
if x % aproximation != 0:
continue
value = thresh[y, x]
value_distance = self._unsgined(last_h_value -value )
last_h_value = value

'''
distance_sum +=distance
distance_count+=1

average_distance = int(distance_sum/distance_count)
print(average_distance)
'''
# does the gray value changed ?
if value_distance > thres:
# strat mesuring from this point
last_x_value = x
last_y_value = y

# mesure the distance where the gray value of pixels are the same
x_distance = self._unsgined(last_x_value -x)
y_distance = self._unsgined(last_y_value -y)

# is there room for the qr code in this distance ?
if x == 0:
last_x_value = x
last_y_value = y

if x_distance >= final_with :
#does the qr code will be cut by the image border ?
if last_x_value+final_with > w:
continue
points.append({"x":last_x_value,"y":last_y_value})
last_x_value = x
last_y_value = y

return points


def _get_pixel_value(self,_image:Image,_x,_y):
im = Image.open('image.gif')
rgb_im = im.convert('RGB')
r, g, b = rgb_im.getpixel((_x, _y))
return r
print(r, g, b)
(65, 100, 137)


def get_blobs(self,_image_path:str,_min=200,_max=8000)->list:

return self._detect_contrast_points(_image_path,_min)

# read image
img = cv2.imread(_image_path)

# convert img to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# do adaptive threshold on gray image
thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 101, 3)

# apply morphology open then close
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))
blob = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (9,9))
blob = cv2.morphologyEx(blob, cv2.MORPH_CLOSE, kernel)

# invert blob
blob = (255 - blob)

# Get contours
cnts = cv2.findContours(blob, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
#big_contour = max(cnts, key=cv2.contourArea)

filtered_contours = []
bounding_boxes = []

min = 100
max = 300000
for contour in cnts:
blob_area = cv2.contourArea(contour)
if blob_area < min:
continue
if blob_area > max:
continue
filtered_contours.append(contour)
rect = cv2.minAreaRect(contour)
box = cv2.boxPoints(rect)
box = np.int0(box)
bounding_boxes.append(box)
print(box)
...

result = img.copy()

cv2.drawContours(result, filtered_contours, -1, (0,0,255), 1)
for box in bounding_boxes:
cv2.drawContours(result,[box],0,(0,255,0),2)

cv2.imwrite("doco3_contour.jpg", result)
cv2.imshow("RESULT", result)
cv2.waitKey(0)
return bounding_boxes

# test blob size
blob_area = cv2.contourArea(big_contour)
if blob_area < blob_area_thresh:
print("Blob Is Too Small")

# draw contour
result = img.copy()
cv2.drawContours(result, [big_contour], -1, (0,0,255), 1)

# write results to disk
cv2.imwrite("doco3_threshold.jpg", thresh)

# display it
return
cv2.imshow("IMAGE", img)
cv2.imshow("THRESHOLD", thresh)
147 changes: 147 additions & 0 deletions src/classes/Combiner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
from PIL import Image
import os
import shutil
import uuid
from classes.ImageChecker import ImageChecker
from classes.ImageEditor import ImageEditor

class Combiner():

_IC = ImageChecker()
_IE:ImageEditor = ImageEditor()

def __init__(self):
self._axe= "H"
self._crop = True
...

def combine(self,_paths:list)->str:
print(_paths)
images = _paths
if self._crop:
images = []
for path in _paths:
if self._IC.check(path)==None:
return
images.append(self._crop_to_bbox(path))
temp = self._generate_tmp_path()+".png"
if self._axe == "H":
self._combine_images_horizontaly(images,temp)
if self._axe == "V":
self._combine_images_verticaly(images,temp)
return temp

def set_axe(self,_axe:str):
self._axe = _axe
return self

def set_crop(self,_c:bool):
self._crop = _c
return self

def _combine_images_verticaly(self,_paths,_output,_padding=100):
for path in _paths:
if self._IC.check(path) is None:
return
images = [Image.open(x) for x in _paths]
widths, heights = zip(*(i.size for i in images))

total_height = sum(heights)+(_padding*(len(_paths)+1))
max_width = max(widths)

new_im = Image.new('RGBA', (max_width,total_height))
path = self._IE.resize_by_width(self._IC.get_max_width())

x_offset = 0
y_offset = _padding

for im in images:
new_im.paste(im, (x_offset,y_offset))
x_offset += im.size[1]+_padding

new_im.save(_output)
return new_im

def _conform_images_width(self,_paths,_total_width)->list:
# check if the combined image is not too hudge (will cause errors on PIL side)
max_auhorised_width = self._IC.get_max_width()
conformed_paths = _paths
if _total_width >= max_auhorised_width:
print("[Combiner] WARNING ! final width is too high , resizing images ! ")
conformed_paths = []
forced_equal_width = int((max_auhorised_width/len(_paths))*0.95)
for path in _paths:
resized_image = self._IE.resize_by_width(path,forced_equal_width)
conformed_paths.append(resized_image)
return conformed_paths

def _combine_images_horizontaly(self,_paths,_output,_padding=100):
for path in _paths:
if self._IC.check(path)==None:
return

# original width sum and max heigth
images = [Image.open(x) for x in _paths]
widths, heights = zip(*(i.size for i in images))
total_width = sum(widths)+(_padding*(len(_paths)+1))
max_height = max(heights)

#width check
conformed_paths = self._conform_images_width(_paths,total_width)
conformed_images = [Image.open(x) for x in conformed_paths]

# conformed width sum and max heigth
widths, heights = zip(*(i.size for i in conformed_images))
total_width = sum(widths)+(_padding*(len(_paths)+1))
max_height = max(heights)

new_im = Image.new('RGBA', (total_width, max_height))
max_height = max(heights)

x_offset =_padding
y_offset = 0

for im in conformed_images:
new_im.paste(im, (x_offset,y_offset))
x_offset += im.size[0]+_padding

new_im.save(_output)
return new_im

def _crop_to_bbox(self,_path:str)->str:

# Opens a image in RGB mode
im = Image.open(_path)
bbox = im.getbbox()

# Size of the image in pixels (size of original image)
# (This is not mandatory)
width, height = im.size

# Cropped image of above dimension
# (It will not change original image)
im1 = im.crop(bbox)

# Shows the image in image viewer
temp = self._generate_tmp_path()
ext = "."+_path.split(".")[-1]
im1.save(temp+ext)
return temp+ext

def _generate_tmp_path(self)->str:
image_folder= os.getenv("TEMP")
image_name = str(uuid.uuid4())[-15:]
path = image_folder+"/"+image_name
return path

def _copy_image_to_temp(self,_path):
image_temp = os.getenv("TEMP")
shutil.copy(_path, os.getenv("TEMP"))
return image_temp+"/"+os.path.basename(_path)


'''
python P:/pipeline/dev/a.cormier/core/decorators/image_stamp/repos/ImageStamp/src/main.py -combine -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0001.png" -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0002.png" -o "P:/projects/riv/temp_no_backup/image_stamp"
python P:/pipeline/dev/a.cormier/core/decorators/image_stamp/repos/ImageStamp/src/main.py -combine -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0001.png" -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0002.png" -i "P:/projects/billy/render/png/assets/library/master/ch_billy/t-0003.png" -o "P:/projects/riv/temp_no_backup/image_stamp"

'''
Loading