{"nbformat_minor": 1, "cells": [{"source": ["In this Kernel, I'd like to show you a very basic segmentation technique whihc only applies pure computer vision techniques. Nothing fancy.\n", "\n", "At first, I'll show the step-by-step processing and after that I will create the submission for the competition.\n", "\n", "With this kernel, I could reach *0.229 LB* which is not very nice but I am sure that with a few tweaks we could get better score. And consider that **we don't even use the train data**! which is pretty awesome in my opinion."], "cell_type": "markdown", "metadata": {"_uuid": "c337bb4f41c90a4c45decf5d66a2aea2a41a9d7d", "_cell_guid": "86e8e6f7-b9af-4927-9a60-d9ef4d5cf059"}}, {"source": ["import numpy as np\n", "import pandas as pd\n", "import os\n", "from os.path import join\n", "import glob\n", "import cv2\n", "%matplotlib inline\n", "import matplotlib.pyplot as plt"], "outputs": [], "cell_type": "code", "metadata": {"_uuid": "b14671a1b5787c6aaa46cdcf51499ef45754ca42", "collapsed": true, "_cell_guid": "e598f333-b831-4c7b-a6c7-8a1beb532e88"}, "execution_count": null}, {"source": ["TRAIN_PATH = '../input/stage1_train/'\n", "TEST_PATH = '../input/stage1_test/'"], "outputs": [], "cell_type": "code", "metadata": {"_uuid": "136dd5ba7ca3b388cd5b75f7418dbddb6f1cb411", "collapsed": true, "_cell_guid": "427ce69b-1f31-444e-b4d3-1c512c141be9"}, "execution_count": null}, {"source": ["train_ids = os.listdir(TRAIN_PATH)\n", "test_ids = os.listdir(TEST_PATH)"], "outputs": [], "cell_type": "code", "metadata": {"_uuid": "d06001c69588dd05f599a753f81ba043471e7d3b", "collapsed": true, "_cell_guid": "da1120e3-00d2-40b9-8805-2cb42c5b8003"}, "execution_count": null}, {"source": ["test_image_paths = [glob.glob(join(TEST_PATH, test_id, \"images\", \"*\"))[0] for test_id in test_ids]"], "outputs": [], "cell_type": "code", "metadata": {"_uuid": "913640aefe0b32d9581d06eec25ff7c48b6c6301", "collapsed": true, "_cell_guid": "a0dd4560-fd97-4b6b-99df-e29aced4f31d"}, "execution_count": null}, {"source": ["# Step-by-step processing"], "cell_type": "markdown", "metadata": {"_uuid": "81feefe80ea104bece709c4b03df5f6dfdbc02cb", "_cell_guid": "890959d8-4f7e-4f2c-9436-d71d8bae699c"}}, {"source": ["tmp_image_path = np.random.choice(test_image_paths)\n", "tmp_image = cv2.imread(tmp_image_path, cv2.IMREAD_GRAYSCALE)"], "outputs": [], "cell_type": "code", "metadata": {"_uuid": "e56f54b4d6af4cb1bb86294dae168ad82ebc6e2e", "collapsed": true, "_cell_guid": "426f8b45-bb84-4115-9e09-3ef65c28b1e9"}, "execution_count": null}, {"source": ["plt.imshow(tmp_image)"], "outputs": [], "cell_type": "code", "metadata": {"_uuid": "c6a6ac4ed1f189ced52b62da00b241cae5884d0f", "collapsed": true, "_cell_guid": "90514c59-d275-48a8-a99e-09d70717dc3f"}, "execution_count": null}, {"source": ["Now comes the crucial part of the processing. First we would like to create a binary matrix from the original image. The ones in the matrix are considered to be objects and the zeros are the background. So If we don't do this correctly we're going to loose a lot of inforamtion."], "cell_type": "markdown", "metadata": {"_uuid": "b5083e36e0c1eeb971d2540bb69d0730f6593b15", "_cell_guid": "cb830fda-7633-4bb5-9bf4-2bbb46f01c2b"}}, {"source": ["ret, thresh = cv2.threshold(tmp_image, 100, 255, cv2.THRESH_OTSU)"], "outputs": [], "cell_type": "code", "metadata": {"_uuid": "4049b8260daaa5d90acdc0e13d8b00d128853eb6", "collapsed": true, "_cell_guid": "b979fc30-63a9-4c82-8663-4003bd1077b3"}, "execution_count": null}, {"source": ["fig, axs = plt.subplots(1, 2, figsize=(10,10))\n", "axs[0].imshow(tmp_image)\n", "axs[1].imshow(thresh)"], "outputs": [], "cell_type": "code", "metadata": {"_uuid": "25465720536af4ddaa6a3ad7a6420fec10c109f5", "collapsed": true, "_cell_guid": "083e1f41-d9c9-4485-ae7a-e92f37593f4b"}, "execution_count": null}, {"source": ["There are images where the thresholding does not help because the ones will be the background and the zeros the objects. This happend when the background is more brighter than the objects.\n", "\n", "But how we detect this?\n", "\n", "We just have to find the contours of the objects. Than calculate the area of the contour and if it is above some threshold value than we will just invert the image."], "cell_type": "markdown", "metadata": {"_uuid": "9da74b0cf39608dbed9bf5aea0aa4cc4b9fc6a10", "_cell_guid": "80167913-9c21-483e-a47e-3dfb8c468338"}}, {"source": ["_, cnts, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)\n", "cnts = sorted(cnts, key=cv2.contourArea, reverse=True)"], "outputs": [], "cell_type": "code", "metadata": {"_uuid": "ce0e05cf14294d3f7a154104a8f1a48b8f5a8d20", "collapsed": true, "_cell_guid": "0ea702aa-49b8-4e94-b00b-9624504d2fe9"}, "execution_count": null}, {"source": ["max_cnt_area = cv2.contourArea(cnts[0])"], "outputs": [], "cell_type": "code", "metadata": {"_uuid": "b8d9ecdb2099622817db8410d523ab71c57ecf1e", "collapsed": true, "_cell_guid": "55845377-4503-4ab5-8b3d-004f955d2e00"}, "execution_count": null}, {"source": ["print(\"The area of the largest object is: {0}\".format(max_cnt_area))"], "outputs": [], "cell_type": "code", "metadata": {"_uuid": "efd348cc7978b611a9c26bef0e09ae305f616fdc", "collapsed": true, "_cell_guid": "57fe65ba-7c68-4fa9-853d-354eff3601e6"}, "execution_count": null}, {"source": ["This is the part where we invert the threshold image if we are not satisfied with the area of the largest contour"], "cell_type": "markdown", "metadata": {"_uuid": "71d2c23e7c17872822ff62313d927f4e76d8ef9e", "_cell_guid": "3562fe77-237a-494d-988b-0bff4b819c5e"}}, {"source": ["if max_cnt_area > 50000:\n", "    ret, thresh = cv2.threshold(tmp_image, 100, 255, cv2.THRESH_OTSU | cv2.THRESH_BINARY_INV)"], "outputs": [], "cell_type": "code", "metadata": {"_uuid": "8a3bdf11d0618b0d9c48790bddc9626b1f33072c", "collapsed": true, "_cell_guid": "161c5eb0-a04e-47f3-b9be-b88bca61bca7"}, "execution_count": null}, {"source": ["And here comes the *morphology*.\n", "\n", "We will use:\n", "- Dilation (read more: https://homepages.inf.ed.ac.uk/rbf/HIPR2/dilate.htm)\n", "- Erosion (read more: https://homepages.inf.ed.ac.uk/rbf/HIPR2/erode.htm)"], "cell_type": "markdown", "metadata": {"_uuid": "4299daeca4e3a30a06845155cb0ad81d8c90f261", "_cell_guid": "41194ed3-1be5-43aa-a754-6f63b0edbd21"}}, {"source": ["mask = cv2.dilate(thresh, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5)))\n", "mask = cv2.erode(mask, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5)))"], "outputs": [], "cell_type": "code", "metadata": {"_uuid": "2828de34c17080c367bb7a051dba06ecf93fa5ee", "collapsed": true, "_cell_guid": "f4b1d1c0-361d-498f-ae29-8eebc27cfc41"}, "execution_count": null}, {"source": ["fig, axs = plt.subplots(1, 4, figsize=(30,30))\n", "axs[0].imshow(tmp_image)\n", "axs[1].imshow(thresh)\n", "axs[2].imshow(mask)\n", "axs[3].imshow(cv2.bitwise_and(tmp_image, tmp_image, mask=mask))"], "outputs": [], "cell_type": "code", "metadata": {"_uuid": "a479ea39c7889bf1eb232df189d6c05812044488", "collapsed": true, "_cell_guid": "d66fadc9-ae9b-4be9-9dc3-92dd4439793e"}, "execution_count": null}, {"source": ["# Process the test images for submission"], "cell_type": "markdown", "metadata": {"_uuid": "281c2cd1244d19dd5cca2e680eb1079de7f20a47", "_cell_guid": "d900aae7-a8c7-472a-a7c1-c405c2a90d69"}}, {"source": ["I separated the logic into 2 funcrtions so it is easier to use it."], "cell_type": "markdown", "metadata": {"_uuid": "a074470349efa3bc33b15cb7e7ce4b7398ca9be8", "_cell_guid": "848e1b83-be79-4530-b49b-96846d579c94"}}, {"source": ["def threshold(image_gray):\n", "    image_gray = cv2.GaussianBlur(image_gray, (7, 7), 1)\n", "    ret, thresh = cv2.threshold(image_gray, 0, 255, cv2.THRESH_OTSU)\n", "    \n", "    _, cnts, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)\n", "    cnts = sorted(cnts, key=cv2.contourArea, reverse=True)\n", "    max_cnt_area = cv2.contourArea(cnts[0])\n", "    \n", "    if max_cnt_area > 50000:\n", "        ret, thresh = cv2.threshold(image_gray, 0, 255, cv2.THRESH_OTSU | cv2.THRESH_BINARY_INV)\n", "    \n", "    return thresh\n", "\n", "def apply_morphology(thresh):\n", "    mask = cv2.dilate(thresh, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5)))\n", "    mask = cv2.erode(mask, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5)))\n", "    return mask"], "outputs": [], "cell_type": "code", "metadata": {"_uuid": "7357411528d58d217118632f14abd32bfd3dc7dc", "collapsed": true, "_cell_guid": "6c81c361-2459-4105-be42-95164307058b"}, "execution_count": null}, {"source": ["Now we only have to create the mask images from the test images"], "cell_type": "markdown", "metadata": {"_uuid": "95338a77695bb4c45c2ae60d89c1aa19a9985b8c", "_cell_guid": "8174c3da-27bc-4803-9c44-36c73317de6f"}}, {"source": ["segmented = []\n", "for test_image_path in test_image_paths:\n", "    tmp_image = cv2.imread(test_image_path, cv2.IMREAD_GRAYSCALE)\n", "    \n", "    thresh = threshold(tmp_image)\n", "    mask = apply_morphology(thresh)\n", "    \n", "    segmented.append(mask)"], "outputs": [], "cell_type": "code", "metadata": {"_uuid": "63abd4d28230c8346c297168f8f298d48f6b2638", "collapsed": true, "_cell_guid": "f0cc48cd-4393-4b59-98e9-0d0ea2877dda"}, "execution_count": null}, {"source": ["# Run length Encoding from https://www.kaggle.com/rakhlin/fast-run-length-encoding-python\n", "\n", "from skimage.morphology import label\n", "\n", "def rle_encoding(x):\n", "    dots = np.where(x.T.flatten() == 1)[0]\n", "    run_lengths = []\n", "    prev = -2\n", "    for b in dots:\n", "        if (b>prev+1): run_lengths.extend((b + 1, 0))\n", "        run_lengths[-1] += 1\n", "        prev = b\n", "    return run_lengths\n", "\n", "def prob_to_rles(x, cutoff=0.5):\n", "    lab_img = label(x > cutoff)\n", "    for i in range(1, lab_img.max() + 1):\n", "        yield rle_encoding(lab_img == i)"], "outputs": [], "cell_type": "code", "metadata": {"_uuid": "cc7b6df4d24c6797b50b919c4ef370e9d3a158bc", "collapsed": true, "_cell_guid": "54d69648-c9a5-4d54-b534-9b9da239fa9b"}, "execution_count": null}, {"source": ["new_test_ids = []\n", "rles = []\n", "for n, id_ in enumerate(test_ids):\n", "    rle = list(prob_to_rles(segmented[n]))\n", "    rles.extend(rle)\n", "    new_test_ids.extend([id_] * len(rle))"], "outputs": [], "cell_type": "code", "metadata": {"_uuid": "d3ad229d4ce913825bdcdd8bdab772a73b192001", "collapsed": true, "_cell_guid": "3d5299a3-be04-433e-b1e8-23f807b7078e"}, "execution_count": null}, {"source": ["submission_df = pd.DataFrame()\n", "submission_df['ImageId'] = new_test_ids\n", "submission_df['EncodedPixels'] = pd.Series(rles).apply(lambda x: ' '.join(str(y) for y in x))"], "outputs": [], "cell_type": "code", "metadata": {"_uuid": "d45c3c014cf81f07edfb0e11942d8ff20f9662e7", "collapsed": true, "_cell_guid": "02a4a2d2-921e-433c-9234-0043a8e3b258"}, "execution_count": null}, {"source": ["submission_df.sample(3)"], "outputs": [], "cell_type": "code", "metadata": {"_uuid": "2d27f71380ee26639297bcbbc4cbf6822d126462", "collapsed": true, "_cell_guid": "006863bf-de97-4a48-8ea9-c751b879afcc"}, "execution_count": null}, {"source": ["if not len(np.unique(submission_df[\"ImageId\"])) == len(test_ids):\n", "    print(\"Submission is not complete\")\n", "    print(\"Missing test ids: {0}\".format(set(test_ids).difference(set(np.unique(submission_df[\"ImageId\"])))))\n", "else:\n", "    print(\"Submission is complete\")"], "outputs": [], "cell_type": "code", "metadata": {"_uuid": "2c18af608c436d9ef8a3984888a9c2c2db5cb58d", "collapsed": true, "_cell_guid": "f927cd7b-5cac-46e0-ba83-1287ceb75813"}, "execution_count": null}, {"source": ["submission_df.to_csv('submission_pure_cv.csv', index=False)"], "outputs": [], "cell_type": "code", "metadata": {"_uuid": "032a35371b8906477f2f4d9782d16a7e63e6deec", "collapsed": true, "_cell_guid": "1c318658-1cf3-4238-b61a-8b890238b44e"}, "execution_count": null}, {"source": [], "outputs": [], "cell_type": "code", "metadata": {"_uuid": "f4517f8c8c0ed7bb0e6c126d223425ba05ef6ca5", "collapsed": true, "_cell_guid": "f383e947-9ac1-481a-a378-664874a20153"}, "execution_count": null}], "metadata": {"kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"}, "language_info": {"mimetype": "text/x-python", "nbconvert_exporter": "python", "name": "python", "version": "3.6.4", "file_extension": ".py", "pygments_lexer": "ipython3", "codemirror_mode": {"version": 3, "name": "ipython"}}}, "nbformat": 4}