{"cells":[{"metadata":{},"cell_type":"markdown","source":"### Leaf detection using OpenCV \n\nThis post explores leaf detection using Hue Saturation Value (HSV) based filtering in OpenCV. HSV values can be obtained from color picker sites like this: https://alloyui.com/examples/color-picker/hsv.html\n\nThere is also a HSV range vizualization on stack overflow thread here:\nhttps://i.stack.imgur.com/gyuw4.png\n\nThe basic idea is to look at green/yellow/brown color ranges and extract only specific HSV values from the image. \n\nThe results are mixed because of the varied lighting and noise in the images but this it is intersting nonetheless. \n"},{"metadata":{"trusted":true},"cell_type":"code","source":"\n# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nimport cv2\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nfrom albumentations import (\n    HorizontalFlip, VerticalFlip, IAAPerspective, ShiftScaleRotate, CLAHE, RandomRotate90,\n    Transpose, ShiftScaleRotate, Blur, OpticalDistortion, GridDistortion, HueSaturationValue,\n    IAAAdditiveGaussianNoise, GaussNoise, MotionBlur, MedianBlur, IAAPiecewiseAffine, RandomResizedCrop,\n    IAASharpen, IAAEmboss, RandomBrightnessContrast, Flip, OneOf, Compose, Normalize, Cutout, CoarseDropout,\n    ShiftScaleRotate, CenterCrop, Resize\n)\nfrom albumentations.pytorch import ToTensorV2\nimport matplotlib.pyplot as plt","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### This is the main function to filter the images based on HSV values. \n\nHere, we create a filter for yellow/green color range\n\n```\n    mask_yellow_green = cv2.inRange(hsv, (10, 39, 64), (86, 255, 255))\n\n```\n\nAnd brown:\n\n```\n    mask_brown = cv2.inRange(hsv, (8, 60, 20), (30, 255, 200))\n\n```\n\nAnd finally a combined mask for green/yellow/brown:\n```\n    mask = cv2.bitwise_or(mask_yellow_green, mask_brown)\n\n```\n\nThe last step is a bitwise AND of images to filter out all other HSV values:\n```\n    res = cv2.bitwise_and(img, img, mask=mask)\n\n```\n\nThe full function is here:"},{"metadata":{"trusted":true},"cell_type":"code","source":"def detect_leaf(img):\n    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)\n    # find the brown color\n    mask_brown = cv2.inRange(hsv, (8, 60, 20), (30, 255, 200))\n    # find the yellow and green color in the leaf\n    mask_yellow_green = cv2.inRange(hsv, (10, 39, 64), (86, 255, 255))\n    # find any of the three colors(green or brown or yellow) in the image\n    mask = cv2.bitwise_or(mask_yellow_green, mask_brown)\n    # Bitwise-AND mask and original image\n    res = cv2.bitwise_and(img, img, mask=mask)\n    return res\n","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"449599e9-a8c6-4d01-a814-401711fffb4b","_cell_guid":"2d86ff04-db7e-4600-b6da-ab4eb68239da","trusted":true},"cell_type":"code","source":"\nSEED = 1234\n\nDATA_PATH = \"../input/cassava-leaf-disease-classification/\"\n# Where the imgaes/audio are stored\nFILE_PATH = \"../input/cassava-leaf-disease-classification/train_images/\"\nTRAIN_DF_FILE = 'train.csv'\nTARGET_COL = 'label'\nID_COL = 'image_id'\n# For predictions\nTEST_FILE_PATH = \"../input/cassava-leaf-disease-classification/test_images/\"\n\n\ndef resize(image, size=None):\n    if size is not None:\n        h, w, _ = image.shape\n        new_w, new_h = int(w * size / h), size\n        image = cv2.resize(image, (new_w, new_h))\n\n    return image\n\n\ndef normalize(image, mean=None, std=None):\n    image = image / 255.0\n    if mean is not None and std is not None:\n        image = (image - mean) / std\n    #return np.moveaxis(image, 2, 0).astype(np.float32)\n    return image.astype(np.float32)\n\n    \n\n# Data loader\nclass CustomDataset(Dataset):\n    def __init__(self, df, file_path, train=True, transforms=None):\n        self.train = train\n        self.df = df\n        self.file_path = file_path\n        self.filename = df[ID_COL].values\n        self.transforms = transforms\n        if self.train:\n            self.y = df[TARGET_COL]\n\n    def __len__(self):\n        return len(self.filename)\n\n    def __getitem__(self, idx: int):\n        # Return audio and sampling rate\n        file = self.file_path + self.filename[idx]\n        image = cv2.imread(file)\n        orig_image = image.copy()\n\n        # Special user defined mask\n        if Config.cv_mask:\n            image = detect_leaf(image)\n        else:\n            image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n\n        # Normalize if no augmentation. Test set also needs this\n        if not self.transforms:\n            image = cv2.resize(image, dsize=(Config.img_size, Config.img_size), interpolation=cv2.INTER_LINEAR)\n            image = normalize(image, mean=None, std=None)\n        # Augment\n        else:\n            #image = cv2.resize(image, dsize=(Config.img_size, Config.img_size), interpolation=cv2.INTER_LINEAR)\n            #image = normalize(image, mean=None, std=None)\n            image = self.transforms(image=image)['image']\n\n        # Uncomment to switch channel to first dimension if image shape is H x W x C\n        # image = np.transpose(image, axes=[2,0,1])\n\n        # Return image and raw data\n        if self.train:\n            return image, ONE_HOT[self.df[TARGET_COL][idx]]\n        elif Config.test_loader:\n            return image, orig_image\n        else:\n            return image\n# Test the data loader and augments\ndef test_loader(df, samples):\n    train_df = df.sample(n=samples)\n    if Config.augment:\n        dataset = CustomDataset(df=train_df, file_path=FILE_PATH, train=False, transforms=get_sample_transforms())\n    else:\n        dataset = CustomDataset(df=train_df, file_path=FILE_PATH, train=False)\n    data_loader = DataLoader(\n        dataset,\n        batch_size=samples,\n        shuffle=True,\n        drop_last=False,\n        pin_memory=True,\n        num_workers=NUM_WORKERS,\n    )\n\n    for images, orig_images in (data_loader):\n        for i in range(len(images)):\n            img = images[i]\n            orig_image = orig_images[i]\n            plt.figure(figsize=(40, 20))\n\n            plt.subplot(1, 2, 1)\n            plt.imshow(orig_image)\n            \n            plt.subplot(1, 2, 2)\n            plt.imshow(img)\n            plt.show()\ndef get_sample_transforms():\n    return Compose([\n        # Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225], max_pixel_value=255.0, p=1.0),\n        RandomResizedCrop(Config.aug_img_size, Config.aug_img_size, p=1),\n        HorizontalFlip(p=0.5),\n        # VerticalFlip(p=0.5),\n        #RandomRotate90(p=0.5),\n        # Transpose(p=0.5),\n        # ShiftScaleRotate(p=0.5),\n        # HueSaturationValue(hue_shift_limit=0.2, sat_shift_limit=0.2, val_shift_limit=0.2, p=0.5),\n        # RandomBrightnessContrast(brightness_limit=(-0.1, 0.1), contrast_limit=(-0.1, 0.1), p=0.5),\n        # CoarseDropout(p=0.5),\n        # Cutout(p=0.5),\n        ToTensorV2(p=1.0),\n    ], p=1.)\n\nNUM_WORKERS = 4\n\nclass Config:\n    augment = False\n    # Value to resize to\n    img_size = 512\n    aug_img_size = 512\n    cv_mask = True\n    test_loader = True\ntrain_df_path = os.path.join(DATA_PATH, TRAIN_DF_FILE)\ntrain_df = pd.read_csv(train_df_path)\n\ntest_loader(train_df,2 )","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat":4,"nbformat_minor":4}