{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 主題:健康上網AI守門員! 色情圖片辨識\n",
    "### 組員:陳怡升、張睿傑、李志恆<br/>\n",
    "[發表簡報請點我](https://docs.google.com/presentation/d/19mwSTLK1vLj8kBd4NtedvkfHCA17YikbTqA5CD2D2Tg/edit?usp=sharing)\n",
    "<br>\n",
    "### 概述:\n",
    "我們打造出一個新型態的<span style=\"color:red\">健康上網守門員</span><br/>透過色情圖片辨識,當偵測到使用者在瀏覽<span style=\"color:red\">色情內容</span>時,將會自動播放歌曲[Big Enough](https://www.youtube.com/watch?v=rvrZJ5C_Nwg)中[牛仔的尖叫片段](https://www.youtube.com/watch?v=Qcp2W1-SFt4),<br/>然後讓滑鼠亂飄移,並<span style=\"color:red\">按下Alt+F4關閉視窗</span>讓使用者無法正常瀏覽色情內容。\n",
    "![AHHH](https://trello-attachments.s3.amazonaws.com/5c6a07f61246780dd23d1ee9/5cf9e77c1b3c9662de99a4de/9d3406ef219953d95918ff4721942cf8/image.png)\n",
    "<br/>程式實際使用示範影片:[有碼版本示範影片](https://www.youtube.com/watch?v=AsDYYk-qPA8&feature=youtu.be)\n",
    "<br/>同場加映:[上傳到PornHub的無碼版本示範影片](https://www.pornhub.com/view_video.php?viewkey=ph5cfbb88976949) (內含18+畫面,慎入!!)\n",
    "\n",
    "\n",
    "### 目的:\n",
    "本來我們想做色情圖片辨識,不過想想辨識出來也要有個用途,靈光一閃就想到這個\"健康上網AI守門員\"的idea<br/>\n",
    "我們有空的話,會在暑假將其改良成開機後自動在背景執行,為我國青少年身心健康發展盡一份心力。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 具體做法:\n",
    "* **資料取得:**<br/>\n",
    "我們將透過github上的[NSFW數據庫](https://github.com/GantMan/nsfw_model)來取得資料進行訓練。<br/>\n",
    "將資料集中corrupted image 移除後剩下的**212038**張圖片被分為5種類別,分別為:<br/>\n",
    "![CLASSES](https://trello-attachments.s3.amazonaws.com/5c6a07f61246780dd23d1ee9/5cf9e77c1b3c9662de99a4de/a695856e957d053665144284c3ca3dd4/%E4%B8%8B%E8%BC%89.png)\n",
    "<br/><br/>\n",
    "* **模型訓練:** <br/>\n",
    "使用Keras中的<span style=\"color:red\">InceptionV3</span>模型中ImageNet 的權重來進行transfer learning,再加上兩層<span style=\"color:red\">DenseNet</span>提高辨識度。<br/><br/>\n",
    "* **訓練結果:** <br/>\n",
    "在經過長時間訓練後,模型實際使用時正確率很高,在將圖片分類上能夠達到<span style=\"color:red\">90%</span> 以上的正確率。<br/><br/>\n",
    "訓練模型建置的github 連結: https://github.com/juichiehchang/NCTS5017-PythonFinalProject<br/><br/>\n",
    "* **程式建置:** <br/>\n",
    "我們把訓練完成的**模型**與**權重**讀入,打造出一個能夠辨識輸入圖片類別的辨識器。<br/>\n",
    "接著使用PIL套件截圖,並將圖片分割為<span style=\"color:red\">18</span>個小圖片進行辨識,如下圖<br/>\n",
    "![CLASSES](https://trello-attachments.s3.amazonaws.com/5c6a07f61246780dd23d1ee9/5ce74f673f93f75eff62e9b1/ac0330eee3fa696814a4df43863c9331/image.png)\n",
    "(我們將螢幕分割為3x5共15張圖片,並在影片播放位置的**重點區域**多剪兩張以及整個畫面一張,總共18張)<br/><br/>\n",
    "該程式將<span style=\"color:red\">定期做螢幕截圖</span>並辨識圖片中是否有色情內容。<br/>\n",
    "如果色情內容的比例超過一定水準,將會使用winsound套件自動播放歌曲Big Enough中牛仔的尖叫片段,<br/>\n",
    "並使用PyAutoGUI讓<span style=\"color:red\">滑鼠隨機飄移無法操控</span>並在十五秒後<span style=\"color:red\">按下ALT+F4關閉視窗</span><br/>\n",
    "而如果判斷色情內容比例超過一定水準,但尚未達到該關閉的程度,<br/>\n",
    "因為可能會有誤判也會達到該水平,故程式會播放「光頭哥哥的嘿嘿」音效來提醒使用者,並將記錄累加,如持續超過一定水準也會執行關閉程式。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 此程式的缺點:\n",
    "使用本模型,有一個缺點,就是**非常耗CPU資源**,因為<span style=\"color:red\">判讀模型很大</span><br/>\n",
    "當執行判斷時,我們的CPU使用率會衝到100%約3秒,如下方圖片所示。<br/>\n",
    "(高峰為程式執行判斷的時候)<br/>\n",
    "![IMAGES](https://trello-attachments.s3.amazonaws.com/5c6a07f61246780dd23d1ee9/5cf9e77c1b3c9662de99a4de/9638d71ae5f2051ebe93f0c0a96d4c99/%E6%93%B7%E5%8F%96.JPG)\n",
    "未來如果要改進,需要尋找有無抑制CPU使用率的寫法,<br/>\n",
    "或將截圖<span style=\"color:red\">上傳至雲端</span>,由雲端的伺服器做判斷後,再將結果回傳給用戶端執行。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 程式碼內容\n",
    "(歡迎複製過去自行使用,其他檔案請至[雲端硬碟下載](https://drive.google.com/file/d/1nRXHMx9T4MLmZBP2rfjc8G8krewEHhaD/view?usp=sharing)整合好的zip檔案)<br/>後面還有趣事(判斷失敗的例子)喔!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\Users\\Public\\ANACONDA3\\lib\\site-packages\\h5py\\__init__.py:36: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.\n",
      "  from ._conv import register_converters as _register_converters\n",
      "Using TensorFlow backend.\n"
     ]
    }
   ],
   "source": [
    "#import predict\n",
    "import cv2\n",
    "import numpy as np\n",
    "from PIL import Image\n",
    "from PIL import ImageGrab\n",
    "import time\n",
    "import pyautogui\n",
    "import winsound\n",
    "#import sys\n",
    "#sys.setrecursionlimit(50)\n",
    "import numpy as np\n",
    "import os\n",
    "os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'\n",
    "import keras\n",
    "\n",
    "def load_image(img_path, size):\n",
    "    image = keras.preprocessing.image.load_img(img_path, target_size = size)\n",
    "    image = keras.preprocessing.image.img_to_array(image)\n",
    "    image /= 255\n",
    "    return np.expand_dims(image, axis=0)\n",
    "\n",
    "class predictor():\n",
    "\n",
    "    model = None\n",
    "    categories = ['drawings', 'hentai', 'neutral', 'porn', 'sexy']\n",
    "    size = None\n",
    "    \n",
    "    def __init__(self, model_path):\n",
    "        if('inceptionV3' in model_path):\n",
    "            self.size = (299, 299)\n",
    "        self.model = keras.models.load_model(model_path)\n",
    "       \n",
    "    def predict_from_path(self, img_path):\n",
    "        \n",
    "        image = load_image(img_path, self.size)\n",
    "        prediction = self.model.predict(image)\n",
    "        #print(prediction)\n",
    "        return self.categories[np.argmax(prediction)]\n",
    "\n",
    "    def predict_from_array(self, img):\n",
    "\n",
    "        image = img/255\n",
    "        prediction = self.model.predict(np.expand_dims(image, axis=0))\n",
    "        return self.categories[np.argmax(prediction)]\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\Users\\Public\\ANACONDA3\\lib\\site-packages\\keras\\engine\\saving.py:327: UserWarning: Error in loading the saved optimizer state. As a result, your model is starting with a freshly initialized optimizer.\n",
      "  warnings.warn('Error in loading the saved optimizer '\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Judge start\n",
      "讚喔\n",
      "1\n",
      "drawings: 10\n",
      "hentai: 0\n",
      "neutral: 6\n",
      "porn: 0\n",
      "sexy: 2\n",
      "Judge end\n",
      "\n",
      "Judge start\n",
      "母湯喔\n",
      "0\n",
      "drawings: 1\n",
      "hentai: 0\n",
      "neutral: 7\n",
      "porn: 10\n",
      "sexy: 0\n",
      "Judge end\n",
      "\n",
      "Judge start\n",
      "讚喔\n",
      "1\n",
      "drawings: 1\n",
      "hentai: 0\n",
      "neutral: 17\n",
      "porn: 0\n",
      "sexy: 0\n",
      "Judge end\n",
      "\n",
      "Judge start\n",
      "讚喔\n",
      "1\n",
      "drawings: 9\n",
      "hentai: 0\n",
      "neutral: 9\n",
      "porn: 0\n",
      "sexy: 0\n",
      "Judge end\n",
      "\n",
      "Judge start\n",
      "讚喔\n",
      "1\n",
      "drawings: 12\n",
      "hentai: 0\n",
      "neutral: 6\n",
      "porn: 0\n",
      "sexy: 0\n",
      "Judge end\n",
      "\n",
      "Judge start\n",
      "讚喔\n",
      "1\n",
      "drawings: 7\n",
      "hentai: 0\n",
      "neutral: 10\n",
      "porn: 1\n",
      "sexy: 0\n",
      "Judge end\n",
      "\n",
      "Judge start\n",
      "讚喔\n",
      "1\n",
      "drawings: 2\n",
      "hentai: 0\n",
      "neutral: 15\n",
      "porn: 1\n",
      "sexy: 0\n",
      "Judge end\n",
      "\n",
      "Judge start\n",
      "讚喔\n",
      "1\n",
      "drawings: 5\n",
      "hentai: 0\n",
      "neutral: 13\n",
      "porn: 0\n",
      "sexy: 0\n",
      "Judge end\n",
      "\n"
     ]
    }
   ],
   "source": [
    "weights_path = \"C:/Users/user/Downloads/pythonF/inceptionV3.299x299.h5\" #請自行修改路徑\n",
    "\n",
    "p = predictor(weights_path)\n",
    "categories = ['drawings', 'hentai', 'neutral', 'porn', 'sexy']\n",
    "    \n",
    "#截圖程式\n",
    "#自動在螢幕產生15個截圖\n",
    "def GrabAndCut():\n",
    "    images = []\n",
    "    width = 300  \n",
    "    height = 300\n",
    "    im=ImageGrab.grab() #截圖\n",
    "    wid_1 = -300\n",
    "    hei_1 = -300\n",
    "    for i in range(3):\n",
    "        wid_1 = -300\n",
    "        for i in range(5):\n",
    "            im_temp = im.crop((   #裁切圖片,裁切出3*5個\n",
    "                width +wid_1,\n",
    "                height + hei_1,\n",
    "                width +wid_1 + 300,\n",
    "                height + hei_1 + 300))\n",
    "            images.append(im_temp.resize((299,299)))\n",
    "            wid_1+=300\n",
    "        hei_1+=300 \n",
    "    images.append(im.crop((           #多剪兩張在平常的播放位置的內容\n",
    "                100,\n",
    "                200,\n",
    "                800,\n",
    "                800)).resize((299,299))) #將圖片大小設定為299x299\n",
    "    images.append(im.crop((\n",
    "                500,\n",
    "                200,\n",
    "                1100,\n",
    "                800)).resize((299,299)))\n",
    "    images.append(im.resize((299,299)))\n",
    "    return images\n",
    "\n",
    "def CheckImages(images):     #分類函式 將圖片分類\n",
    "    result = []\n",
    "    for i in range(len(images)):\n",
    "        result.append(p.predict_from_array(np.array(images[i])))\n",
    "    return result\n",
    "    #判斷時要使用np.array判斷,所以我們將圖片轉換成array\n",
    "    #然後將判斷結果連接到一個叫result的陣列\n",
    "\n",
    "\n",
    "def Judge(result, beforehand):  #根據結果做出判斷的程式\n",
    "    testing = result.count(\"porn\")*2 + result.count(\"hentai\")\n",
    "    #使用自訂的判斷權重,因為在看動漫時較容易判斷出hentai故hentai需要比較高的權重\n",
    "    if testing >=9 or (testing + beforehand) >=11:\n",
    "        winsound.PlaySound(\"AHHH.wav\", winsound.SND_FILENAME|winsound.SND_ASYNC)\n",
    "        #使用winsound播放音樂, winsound.SND_FILENAME 是指播放該檔名的檔案\n",
    "        #winsound.SND_ASYNC 是指播放後就繼續執行剩下的程式碼\n",
    "        for i in range(40):  #亂動滑鼠的部分\n",
    "            pyautogui.moveTo(np.random.randint(1,1910), np.random.randint(1,1070),0.25)\n",
    "        pyautogui.keyDown(\"ALT\")   #使用PyAutoGUI的套件,模擬按下ALT+F4\n",
    "        pyautogui.keyDown(\"F4\")\n",
    "        pyautogui.keyUp(\"ALT\")\n",
    "        #x, y =pyautogui.size()           #移動到右上角關閉視窗\n",
    "        #pyautogui.moveTo(x-10, 10, 10)   #後來選擇ALT+F4,故此段註解\n",
    "        #pyautogui.click()              \n",
    "        print(\"母湯喔\")\n",
    "        Judges = 0\n",
    "    elif testing >=5:\n",
    "        print(\"嘿嘿\")     #播放光頭哥哥的「嘿嘿」音效,警告使用者\n",
    "        winsound.PlaySound(\"hehe.wav\", winsound.SND_FILENAME|winsound.SND_ASYNC)\n",
    "        Judges = 1\n",
    "        beforehand = testing   #將結果回傳,如果重複出現危險內容將會執行關閉程式\n",
    "    else:\n",
    "        print(\"讚喔\")\n",
    "        Judges = 1\n",
    "        beforehand = 0\n",
    "    return Judges, beforehand\n",
    "\n",
    "beforehand = 0\n",
    "#while(1):                  #正式程式,將內容串在一起\n",
    "for i in range(8):          #本來是使用while迴圈,不過因為要呈現不想出現KeyboardInterrupt\n",
    "                            #所以使用for迴圈做八次\n",
    "    print(\"Judge start\")\n",
    "    images = GrabAndCut()\n",
    "    result = CheckImages(images)\n",
    "    Judges, beforehand = Judge(result, beforehand)\n",
    "    print(Judges)\n",
    "    for i in range(len(categories)):     #打印出分類模型究竟分類出來的結果\n",
    "        print(\"%s: %d\"%(categories[i], result.count(categories[i])))\n",
    "    print(\"Judge end\\n\")\n",
    "    time.sleep(1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 趣聞 (判斷失敗的例子) :\n",
    "此程式準確率還是有待加強,比方會將一些新聞的圖片判斷為porn\n",
    "(如下方圖片)<br/>\n",
    "推測可能是因為原先training data **並沒有文字**,同時切割過的圖片較不易判斷<br/>所以我們使用指定公式,當porn分類超過5個,才執行關閉程式。\n",
    "![IMAGE](https://trello-attachments.s3.amazonaws.com/5c6a07f61246780dd23d1ee9/5cf9e77c1b3c9662de99a4de/74eadfc81e7e8158d61fb029b8b473e4/messageImage_1559742855638.jpg)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.3"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": false,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {
    "height": "calc(100% - 180px)",
    "left": "10px",
    "top": "150px",
    "width": "165px"
   },
   "toc_section_display": true,
   "toc_window_display": true
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}