{"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_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Samsung Innovation Campus <br/>Machine Learning Bootcamp 2022\n## Задача 6. Определение качества изображения\n\nПостановка задачи:<br>\nhttps://www.youtube.com/watch?v=8gWG-PLfJDY\n\nBaseline Алены Москаленко из постановки задачи, на основе которого сделан этот ноутбук:<br>\nhttps://colab.research.google.com/drive/1QGeP8ZdydD0JkMZuOuKpbYtz04fpbAjO\n\nРазбор:<br>\nhttps://www.youtube.com/watch?v=7Rh-hawl--0&t=171s\n\n","metadata":{}},{"cell_type":"code","source":"import os\nfrom glob import glob\nimport random\n\nimport cv2\nimport numpy as np\nimport pandas as pd\nfrom tqdm.notebook import tqdm\n\nfrom sklearn.model_selection import cross_val_score\nfrom sklearn.ensemble import RandomForestRegressor\n\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nsns.set_style('whitegrid')\n\n\nsrc_path = \"/kaggle/input/psnr-iqa/SBC22_IQA_dataset/SBC22_IQA_dataset/\"\npath_2_train_csv = \"/kaggle/input/psnr-iqa/train.csv\"\npath_2_test_csv = \"/kaggle/input/psnr-iqa/sample_submission.csv\"\n\ntrain_df = pd.read_csv(path_2_train_csv) \ntest_df = pd.read_csv(path_2_test_csv)\n\n#Тип искажения\ntrain_df[\"pr_type\"]=train_df[\"img_name\"].apply(lambda x: int(x.split(\".\")[0].split(\"_\")[1]) )\ntest_df[\"pr_type\"]=test_df[\"img_name\"].apply(lambda x: int(x.split(\".\")[0].split(\"_\")[1]) )\n\ntrain_df[\"name\"]=train_df[\"img_name\"].apply(lambda x: x.split(\".\")[0].split(\"_\")[0] )\n\n# gen_imgs -- генератор для train вместо imgs из baseline\ndef gen_imgs(inds=[]):\n  if(len(inds)==0):\n    img_names=train_df.img_name.values\n  else:\n    img_names=train_df.loc[inds].img_name.values\n\n  for img_name in img_names:\n    img = cv2.imread(os.path.join(src_path, 'train', img_name))\n    yield img\n\n# генератор для test вместо test_imgs из baseline\ndef gen_test_imgs(inds=[]):\n  if(len(inds)==0):\n    img_names=test_df.img_name.values\n  else:\n    img_names=test_df.loc[inds].img_name.values\n\n  for img_name in img_names:\n    img = cv2.imread(os.path.join(src_path, 'test', img_name))\n    yield img\n    \n# показывает изображение и гистограммы для каждого канала\ndef img_with_hist(index,name):\n    img = list(gen_imgs([index]))[0]\n    f,ax = plt.subplots(1,4,figsize=(20,4))\n    ax[0].imshow(cv2.cvtColor( img, cv2.COLOR_BGR2RGB))\n    ax[0].set_title(name)\n    ax[1].hist(img[:,:,2].reshape(-1), bins=256, edgecolor='black')\n    ax[1].set_title(\"Red\")\n    ax[2].hist(img[:,:,1].reshape(-1), bins=256, edgecolor='black')\n    ax[2].set_title(\"Green\")\n    ax[3].hist(img[:,:,0].reshape(-1), bins=256, edgecolor='black')\n    ax[3].set_title(\"Blue\")\n    \n    plt.show()\n    \n# генерация заданного числа случайных имен референсов\ndef get_random_names(n):\n    names=train_df[\"name\"].unique()\n    inds = np.random.randint(0, len(names),n)\n    return names[inds]\n","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-10-18T13:15:20.783122Z","iopub.execute_input":"2022-10-18T13:15:20.783679Z","iopub.status.idle":"2022-10-18T13:15:20.874739Z","shell.execute_reply.started":"2022-10-18T13:15:20.783630Z","shell.execute_reply":"2022-10-18T13:15:20.873347Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Распределение PSNR для различных типов искажений\n\nВсего используется 5 типов искажений. Тип искажения закодирован цифрой в имени файла после знака подчеркивания.","metadata":{}},{"cell_type":"code","source":"f, ax = plt.subplots(1, 5, figsize=(25,5))\nfor i in range(5):\n  ax[i].hist(train_df.loc[train_df[\"pr_type\"]==(i+1)][\"PSNR\"], bins=50)\n  ax[i].set_title(\"Тип \"+str(i+1))\nplt.show()\n","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-10-18T13:15:20.914281Z","iopub.execute_input":"2022-10-18T13:15:20.914710Z","iopub.status.idle":"2022-10-18T13:15:22.296650Z","shell.execute_reply.started":"2022-10-18T13:15:20.914677Z","shell.execute_reply":"2022-10-18T13:15:22.295151Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Влияние искажений на изображения","metadata":{}},{"cell_type":"markdown","source":"# Тип 1\n\nТип 1 -- размытие. Интенсивность искажения можно оценить по критериям на основе фильтра Собеля (из baseline) и Лапласиана.\n\n## Примеры изображений с искажениями типа 1","metadata":{}},{"cell_type":"code","source":"names=['45208','308828','465566','247720','399851']\nt=1\nfor name in names:\n    img_with_hist(\n        train_df.loc[(train_df[\"name\"]==name)&(train_df[\"pr_type\"]==t)].index[0],\n        name)\n","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-10-18T13:15:22.298892Z","iopub.execute_input":"2022-10-18T13:15:22.299385Z","iopub.status.idle":"2022-10-18T13:15:33.652386Z","shell.execute_reply.started":"2022-10-18T13:15:22.299350Z","shell.execute_reply":"2022-10-18T13:15:33.651058Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Тип 2\nТип 2 -- аддитивный поканальный гауссовский шум. Интенсивность искажения можно оценить по уровню перенасыщения на максимальных и минимальных значениях гистограмм.\n\n## Примеры изображений с искажениями типа 2","metadata":{}},{"cell_type":"code","source":"names=['34500','277025','528527','489014','188850']\nt=2\nfor name in names:\n    img_with_hist(\n        train_df.loc[(train_df[\"name\"]==name)&(train_df[\"pr_type\"]==t)].index[0],\n        name)","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-10-18T13:15:33.654176Z","iopub.execute_input":"2022-10-18T13:15:33.654671Z","iopub.status.idle":"2022-10-18T13:15:44.319310Z","shell.execute_reply.started":"2022-10-18T13:15:33.654619Z","shell.execute_reply":"2022-10-18T13:15:44.317768Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Тип 3\n\nТип 3 -- аддитивный гауссовский шум в компоненте яркости. Интенсивность искажения также можно оценить по уровню перенасыщения на максимальных и минимальных значениях.\n\n## Примеры изображений с искажениями типа 3","metadata":{}},{"cell_type":"code","source":"names=['188824','430160','395633','4760','507312']\nt=3\nfor name in names:\n    img_with_hist(\n        train_df.loc[(train_df[\"name\"]==name)&(train_df[\"pr_type\"]==t)].index[0],\n        name)","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-10-18T13:15:44.322567Z","iopub.execute_input":"2022-10-18T13:15:44.323045Z","iopub.status.idle":"2022-10-18T13:15:55.564468Z","shell.execute_reply.started":"2022-10-18T13:15:44.323003Z","shell.execute_reply":"2022-10-18T13:15:55.562754Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Тип 4\n\nТип 4 -- изменение экспозиции. Интенсивность искажения можно оценить по уровню перенасыщения на максимальных и минимальных значениях, наличию пропусков в гистограмме. Оценку интенсивности искажений можно попробовать улучшить на основе оценок разности соседних значений гистограмм. \n\n## Примеры изображений с искажениями типа 4","metadata":{}},{"cell_type":"code","source":"names=['18903','518213','396768','507223','47603','338774']\nt=4\nfor name in names:\n    img_with_hist(\n        train_df.loc[(train_df[\"name\"]==name)&(train_df[\"pr_type\"]==t)].index[0],\n        name)","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-10-18T13:15:55.566195Z","iopub.execute_input":"2022-10-18T13:15:55.566720Z","iopub.status.idle":"2022-10-18T13:16:08.651400Z","shell.execute_reply.started":"2022-10-18T13:15:55.566671Z","shell.execute_reply":"2022-10-18T13:16:08.650120Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Тип 5\n\nПохоже изображения уменьшали, а потом возвращали к исходному размеру. Критерии оценки интенсивности искажения как и у типа 1 -- на основе фильтра Собеля (из baseline) и Лапласиана.\n\n## Примеры изображений с искажениями типа 5","metadata":{}},{"cell_type":"code","source":"names=['338774','518213','344548','555048','438995']\nt=5\nfor name in names:\n    img_with_hist(\n        train_df.loc[(train_df[\"name\"]==name)&(train_df[\"pr_type\"]==t)].index[0],\n        name)\n","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-10-18T13:16:08.653098Z","iopub.execute_input":"2022-10-18T13:16:08.654125Z","iopub.status.idle":"2022-10-18T13:16:19.572783Z","shell.execute_reply.started":"2022-10-18T13:16:08.654080Z","shell.execute_reply":"2022-10-18T13:16:19.571776Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Признаки","metadata":{}},{"cell_type":"code","source":"def brightness(img):\n    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)\n    v = hsv[..., 2].mean()\n    return v / 255 # normalization\n\ndef sharpness_grad_based(img):\n    gradx = cv2.Sobel(img, cv2.CV_32F, 1, 0, ksize=1)\n    grady = cv2.Sobel(img, cv2.CV_32F, 0, 1, ksize=1)\n    grad = np.sqrt(gradx * gradx + grady * grady)\n    return grad[1: -1, 1: -1].mean()\n\ndef get_iqa_fv(img):\n    # baseline признаки:\n    # ----------------------------\n    fv = np.empty((0,))\n    fv = np.append(fv, brightness(img))\n    fv = np.append(fv, sharpness_grad_based(img))\n    # ----------------------------\n    \n    fv = np.append(fv, np.mean(img))\n    fv = np.append(fv, np.std(img))\n    fv = np.append(fv, np.min(img))\n    fv = np.append(fv, 255-np.max(img))\n    \n    # оценка резкости/размытости\n    fv = np.append(fv, np.log(cv2.Laplacian(img, cv2.CV_64F).var()) )\n    \n    # оценка пропусков в гистограмме\n    a=np.unique(img,return_counts=True)[1]\n    fv = np.append(fv, len(a))\n    \n    # оценки перенасыщения сверху и снизу \n    fv = np.append(fv, a[0])\n    fv = np.append(fv, a[-1])\n    \n    return fv\n\ndef get_features(list_of_imgs):\n    features =[ get_iqa_fv(img) for img in tqdm(list_of_imgs) ]\n    return np.vstack(list(features))","metadata":{"execution":{"iopub.status.busy":"2022-10-18T13:16:19.574329Z","iopub.execute_input":"2022-10-18T13:16:19.574685Z","iopub.status.idle":"2022-10-18T13:16:19.586239Z","shell.execute_reply.started":"2022-10-18T13:16:19.574653Z","shell.execute_reply":"2022-10-18T13:16:19.585248Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Кроссвалидация\n\nДля каждого типа искажений своя модель. Оценка по MAE (один из вариантов расчета PSNR как раз и есть MAE). Логарифмирование таргета дает улучшение метрики, но на кроссвалидации для упрощения не используется.","metadata":{}},{"cell_type":"code","source":"for t in train_df[\"pr_type\"].unique():\n    print(\"Тип\",t)\n    ids=train_df.loc[train_df[\"pr_type\"]==t].index\n    X = get_features(gen_imgs(ids))\n    y=train_df.loc[ids,\"PSNR\"]\n    print(\"MAE:\",\n          -np.mean(cross_val_score(RandomForestRegressor(),X,y,scoring='neg_mean_absolute_error')) )\n    print(\"---------------------------------------\")\n    ","metadata":{"execution":{"iopub.status.busy":"2022-10-18T13:16:19.587777Z","iopub.execute_input":"2022-10-18T13:16:19.588242Z","iopub.status.idle":"2022-10-18T13:36:30.074967Z","shell.execute_reply.started":"2022-10-18T13:16:19.588194Z","shell.execute_reply":"2022-10-18T13:36:30.073621Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Лучше всего модель работает для изображений, искаженных внесением шума. Хуже всего -- для изображений, потерявших детали за счет размытия или изменения размера. \n\nПомимо умышленно привнесенных искажений в изображениях присутствуют искажения сделанные на этапе получения референсов: в процессе съемки и/или обработки. За счет этого можно попробовать улучшить модель. Можно попробовать, используя все 5 искаженных изображений, получить оценки искажений референса. И относительно полученных оценок искажений референса строить оценки для искаженных изображений.\n\nОценку интенсивности искажений, связанных с экспозицией (тип 4), можно попробовать улучшить на основе оценок, связанных с разностью соседних значений гистограмм: гребни на гистограммах не всегда являются пропусками значений.\n\nМожно попробовать улучшить модель, получая признаки для каждого цветового канала отдельно.\n\nТакже можно попробовать улучшить модель, подбирая гиперпараметры для RF :) ","metadata":{}},{"cell_type":"markdown","source":"# Получение ответа","metadata":{}},{"cell_type":"code","source":"# Предсказание\nfor t in train_df[\"pr_type\"].unique():\n  ids_train=train_df.loc[train_df[\"pr_type\"]==t].index\n  ids_test=test_df.loc[test_df[\"pr_type\"]==t].index\n  \n  print(\"Тип\",t)\n  print(\"train:\")\n  X_train=get_features(gen_imgs(ids_train))\n  print(\"test:\")\n  X_test=get_features(gen_test_imgs(ids_test))\n  y_train=np.log(train_df.loc[ids_train,\"PSNR\"])\n  print(\"--------------------------\")\n    \n  estimator=RandomForestRegressor()\n  estimator.fit(X_train,y_train)\n  test_df.loc[ids_test,\"PSNR\"]=np.exp(estimator.predict(X_test))\n\n# Сохранение результатов\nans = pd.DataFrame({'img_name': test_df.img_name.values, 'PSNR': test_df.PSNR.values})\nans.to_csv(\"submission.csv\", index=False)\n","metadata":{"execution":{"iopub.status.busy":"2022-10-18T13:36:30.076833Z","iopub.execute_input":"2022-10-18T13:36:30.077183Z","iopub.status.idle":"2022-10-18T13:58:48.177986Z","shell.execute_reply.started":"2022-10-18T13:36:30.077152Z","shell.execute_reply":"2022-10-18T13:58:48.176590Z"},"trusted":true},"execution_count":null,"outputs":[]}]}