Fourier analysis of tiff images using openCV

一曲冷凌霜 提交于 2020-05-30 08:12:36

问题


Image Loading

To start I tried loading in .tif images using openCV but got the following error: Invalid bitsperpixel value read from TIFF header! Must be 1, 8, 16, 32 or 64. in function 'cv::TiffDecoder::readHeader'

Checked using a hex editor to determine the bit depth of the image and it seems to be 16bit.

After some trail and error I managed to find a solution using the code below. First loaded the image with the specialized tifffile library. Then saved it to a temporary file and finally loaded it back in again with openCV. (Directly trying to edit the image read from Tifffile using openCV functions didn't work)

raw_image = tifffile.imread(file)
raw_image <<= 4
name = random_string(10) + '.tif'
tifffile.imwrite(name, raw_image)
image = cv2.imread(file, 0)
os.remove(name)

Processing

As the purpose of my program is to detect and track the droplets present on the image, the periodic stripe pattern/interference makes the tracking algorithm struggle. In a previous question a solution was proposed and initially gave promising results: Removal of horizontal stripes using openCV2

Now after some more testing I'm getting the following result:

Succes depends on the files used.

The following link contains a folder with images it does work on and one with images it doesn't work on. https://drive.google.com/file/d/1ifsae0bl2CRoR2ydlQlf35FlzagmeryL/view?usp=sharing

Something worth mentioning is that this processing is done on a large set of images (+-5000). To speed things up I used multiprocessing as implemented below.

 with concurrent.futures.ProcessPoolExecutor(max_workers=3) as executor:
            results = executor.map(self.process_image, file_list)
            for result in results:
                self.image_array.append(result)

    def process_image(self, file):
    try:
        # first the image is read by the tifffile library because openCV can't interpret the
        # proprietary bit depth of the provided images. All the bits of the image are shifted
        # to acquire an image with the right bit depth. A temporary file with a randomly generated
        # name is created because openCV couldn't directly open the shifted image directly.

        raw_image = tifffile.imread(file)
        raw_image <<= 4
        name = random_string(10) + '.tif'
        tifffile.imwrite(name, raw_image)
        image = cv2.imread(name, 0)
        os.remove(name)

        cv2.imshow('test', image)
        cv2.waitKey(0)

        # removal of periodic interference
        image = self.remove_periodic_interference(image)

        cv2.imshow('test', image)
        cv2.waitKey(0)

        # application of mask
        image = self.aplly_mask(image)

        cv2.imshow('test', image)
        cv2.waitKey(0)

        # image now ready for droplet detection
        return image
    except cv2.error as e:
        print(e, 'check if the image files are present')

# removal of periodic stripe pattern in the lower half of the provided image
# using fourier analysis (fft = fast fourier transformation)
def remove_periodic_interference(self, img):
    hh, ww = img.shape

    # get min and max and mean values of img
    img_min = np.amin(img)
    img_max = np.amax(img)
    img_mean = int(np.mean(img))

    # pad the image to dimension a power of 2
    hhh = math.ceil(math.log2(hh))
    hhh = int(math.pow(2, hhh))
    www = math.ceil(math.log2(ww))
    www = int(math.pow(2, www))
    imgp = np.full((hhh, www), img_mean, dtype=np.uint8)
    imgp[0:hh, 0:ww] = img

    # convert image to floats and do dft saving as complex output
    dft = cv2.dft(np.float32(imgp), flags=cv2.DFT_COMPLEX_OUTPUT)

    # apply shift of origin from upper left corner to center of image
    dft_shift = np.fft.fftshift(dft)

    # extract magnitude and phase images
    mag, phase = cv2.cartToPolar(dft_shift[:, :, 0], dft_shift[:, :, 1])

    # get spectrum
    spec = np.log(mag) / 20
    min, max = np.amin(spec, (0, 1)), np.amax(spec, (0, 1))

    # threshold the spectrum to find bright spots
    thresh = (255 * spec).astype(np.uint8)
    thresh = cv2.threshold(thresh, 155, 255, cv2.THRESH_BINARY)[1]

    # cover the center rows of thresh with black
    yc = hhh // 2
    cv2.line(thresh, (0, yc), (www - 1, yc), 0, 5)

    # get the y coordinates of the bright spots
    points = np.column_stack(np.nonzero(thresh))
    print(points)

    # create mask from spectrum drawing horizontal lines at bright spots
    mask = thresh.copy()
    for p in points:
        y = p[0]
        cv2.line(mask, (0, y), (www - 1, y), 255, 5)

    # apply mask to magnitude such that magnitude is made black where mask is white
    mag[mask != 0] = 0

    # convert new magnitude and old phase into cartesian real and imaginary components
    real, imag = cv2.polarToCart(mag, phase)

    # combine cartesian components into one complex image
    back = cv2.merge([real, imag])

    # shift origin from center to upper left corner
    back_ishift = np.fft.ifftshift(back)

    # do idft saving as complex output
    img_back = cv2.idft(back_ishift)

    # combine complex components into original image again
    img_back = cv2.magnitude(img_back[:, :, 0], img_back[:, :, 1])

    # crop to original size
    img_back = img_back[0:hh, 0:ww]

    # re-normalize to 8-bits in range of original
    min, max = np.amin(img_back, (0, 1)), np.amax(img_back, (0, 1))
    notched = cv2.normalize(img_back, None, alpha=img_min, beta=img_max, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U)

    return notched

# application of mask to isolate the usable 'droplets' from each image
# TODO-- optimization --
# TODO-- adjust offset--
def aplly_mask(self, image):
    th, threshed = cv2.threshold(image, 116, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C | cv2.ADAPTIVE_THRESH_MEAN_C)

    # Find the min-area contour
    cnts = cv2.findContours(threshed, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE, offset=(1, 1))[-2]
    cnts = sorted(cnts, key=cv2.contourArea)

    # Manual scaling of contours
    for c in cnts:

        M = cv2.moments(c)

        if M["m00"] != 0:
            cx = int(M["m10"] / M["m00"])
            cy = int(M["m01"] / M["m00"])
        else:
            cx, cy = 0, 0

        scale = 1.5

        cnt_norm = c - [cx, cy]
        cnt_scaled = cnt_norm * scale
        cnt_scaled = cnt_scaled + [cx, cy]
        cnt_scaled = cnt_scaled.astype(np.int32)

    # Mask application
    mask = np.zeros(image.shape[:2], np.uint8)
    cv2.drawContours(mask, cnts, -1, 255, -1)
    dst = cv2.bitwise_and(image, image, mask=mask)
    dst = cv2.bitwise_not(dst, dst)

    return dst

来源:https://stackoverflow.com/questions/61738477/fourier-analysis-of-tiff-images-using-opencv

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!