Khalil Meftah
Khalil Meftah

Reputation: 73

Concatenate/merge images with opencv python

I'm trying to create texture-based image from handwriting manuscripts. After some preprocessing to the input images (binary image of text line form IAM database), segmented the lines into words/charachers using vertical profile projection. The segmented words/charachers are in diffrent size, and i want to concatinate/merge it to form the desired texture-based image. the size of the outputs images make the concatination impossible. I'm using openCV with python to do this, i want some ideas or methods to do such task. This method was inspired by this article : "Writer verification using texture-based features" by R. K. Hanusiak the article link in pages 219-220.

Concatenated images alined with it's center of mass

Text sample on the left, and it's texture-base images on the right

Upvotes: 1

Views: 1404

Answers (1)

Gustavo Kaneto
Gustavo Kaneto

Reputation: 683

Here is a possible solution. You'll have to tweak some parameters, of course...

What my example code does:

  • apply threshold and invert (bitwise_not) the image to get a binary image with black background and white letters
  • apply a small dilate to merge some small elements and decrease the number of detections
  • use findContours to... find contours :)
  • calculate boundingRect and area for each contour, returning rectangles where writings are detected (area can be used to filter small unwanted elements)
  • prepare an image overlapping the source image with contours and rectangles (this part is necessary just to debug)

After detection, the code proceed creating the new "texture image" you want:

  • total_width is the sum of all rectangles widths
  • mean_height is the mean of all rectagles heights
  • total_lines is the number of lines in the new image; calculated from total_width and mean_height, so that the resulting image is approximately square
  • inside a loop, we will copy each rectangle from the src image to the newImg
  • curr_line and curr_width tracks the position where to paste the src rectangle
  • I've used cv.min() to blend each new rectangle into newImg; this is similar to "darken" blending mode in photoshop

The image showing detections:

enter image description here

The resulting texture image:

enter image description here

An the code...

import cv2 as cv
import numpy as np
import math

src = cv.imread("handwriting.jpg")
src_gray = cv.cvtColor(src,cv.COLOR_BGR2GRAY)

# apply threshold
threshold = 230
_, img_thresh = cv.threshold(src_gray, threshold, 255, 0)
img_thresh = cv.bitwise_not(img_thresh)

# apply dilate
dilatation_size = 1
dilatation_type = cv.MORPH_ELLIPSE
element = cv.getStructuringElement(dilatation_type, (2*dilatation_size + 1, 2*dilatation_size+1), (dilatation_size, dilatation_size))
img_dilate = cv.dilate(img_thresh, element)

# find contours
contours = cv.findContours(img_dilate, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)

# calculate rectangles and areas
boundRect = [None]*len(contours[1])
areas = [None]*len(contours[1])
for i, c in enumerate(contours[1]):
    boundRect[i] = cv.boundingRect(c)
    areas[i] = cv.contourArea(c)

# set drawing 
drawing = np.zeros((src.shape[0], src.shape[1], 3), dtype=np.uint8)

# you can use only contours bigger than some area
for i in range(len(contours[1])):
    if areas[i] > 1:
        color = (50,50,0)
        cv.rectangle(drawing, (int(boundRect[i][0]), int(boundRect[i][1])), \
          (int(boundRect[i][0]+boundRect[i][2]), int(boundRect[i][1]+boundRect[i][3])), color, 2)

# set newImg
newImg = np.ones((src.shape[0], src.shape[1], 3), dtype=np.uint8)*255
total_width = 0
mean_height = 0.0
n = len(boundRect)
for r in (boundRect):
    total_width += r[2]
    mean_height += r[3]/n

total_lines = math.ceil(math.sqrt(total_width/mean_height))
max_line_width = math.floor(total_width/total_lines)

# loop through rectangles and perform a kind of copy paste
curr_line = 0
curr_width = 0
for r in (boundRect):
    if curr_width > max_line_width:
        curr_line += 1
        curr_width = 0
    # this is the position in newImg, where to insert source rectangle
    pos = [curr_width, \
           curr_width + r[2], \
           math.floor(curr_line*mean_height), \
           math.floor(curr_line*mean_height) + r[3] ]
    s = src[r[1]:r[1]+r[3], r[0]:r[0]+r[2], :]
    d = newImg[pos[2]:pos[3], pos[0]:pos[1], :]
    newImg[pos[2]:pos[3], pos[0]:pos[1], :] = cv.min(d,s)
    curr_width += r[2]

cv.imwrite('detection.png',cv.subtract(src,drawing))
cv.imshow('blend',cv.subtract(src,drawing))

crop = int(max_line_width*1.1)
cv.imwrite('texture.png',newImg[:crop, :crop, :])
cv.imshow('newImg',newImg[:crop, :crop, :])

cv.waitKey()

Upvotes: 1

Related Questions