2019年6月23日 星期日

C#呼叫C++進行影像處理

當使用C#進行影像處理的時候,如果遇上了需要大量運算的部分,C#可能不太夠力(C#的指標運算受到了限制而且在運算過程中做了很多邊界檢查),這時可以考慮呼叫C++的函式處理這些影像。

在開始之前可以先參考這篇文章:
https://corettainformation.blogspot.com/2019/06/cc.html



我們以C# Window Forms App作為範例。




我們拉一個Panel(裝PictureBox用的容器)進表單中,然後讓它填滿整個表單。

然後拉一個PictureBox(圖片),讓它填滿表單,樣式設定為Zoom(這樣就可以等比例縮放影像)

接著拉一個MenuStrip(選單)進表單中,設定兩組選項("載入影像"和"處理")


最後拉一個openFileDialog(開啟檔案用的對話方塊)進表單中。


這樣子需要的元件已經都佈置好了。


然後在表單的程式碼中新增一個Bitmap(點陣圖)


回到表單設計頁面,將"載入影像"點兩下,再次進入程式碼編輯器中。

鍵入以下程式碼


private void 載入影像ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            openFileDialog1.Filter = "圖片檔 (*.png;*.jpg;*.bmp;*.gif;*.tif)|*.png;*.jpg;*.bmp;*.gif;*.tif";
            if (openFileDialog1.ShowDialog() == DialogResult.OK)
            {
                bitmap = new Bitmap(openFileDialog1.FileName);//將選擇的影像載入至bitmap中
                pictureBox1.Image = bitmap;//將bitmap顯示在pictureBox中
            }
        }

解說:
openFileDialog1.Filter可限制檔案的類型,我們選png、jpg、bmp、gif、tif這五種。
使用openFileDialog1.ShowDialog()可開啟瀏覽檔案的對話方塊,當DialogResult.OK(開啟成功)時會將檔案路徑(openFileDialog1.FileName)傳給Bitmap產生新的點陣物件,我們將這個點陣影像指派給bitmap,最後設定PictureBox的影像為這個點陣圖。



接著測試程式是否能正常執行,執行程式後,按下載入影像/選擇要瀏覽的圖片/按下開啟舊檔。



如果能正常顯示影像,代表到目前為只是成功的,可以繼續做下去。


回到表單設計頁面,點兩下"處理",進入程式碼編輯器中。


接著鍵入以下程式碼。
註:必須在標頭引入 using System.Drawing.Imaging;

Bitmap MyNewBmp = bitmap;
Rectangle MyRec = new Rectangle(0, 0, MyNewBmp.Width, MyNewBmp.Height);
BitmapData MyBmpData = MyNewBmp.LockBits(MyRec, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
unsafe
{
          //empty
}
MyNewBmp.UnlockBits(MyBmpData);
pictureBox1.Image = MyNewBmp;

解說:
Rectangle可以是一種矩形範圍,此範例為(0,0)的座標到(總寬度,總高度)的矩形範圍(意即選取了整張影像)
BitmapData 是像素資料。
LockBits可將點陣影像鎖定在系統記憶體內,可提高處理效能(但是最後務必使用UnlockBits來解鎖)。
MyNewBmp.LockBits的參數中第一個放入MyRec(剛剛的矩形範圍),第二個是數字3(ImageLockMode是列舉,其中的ReadWrite是列舉值3,代表這個BitmapData可以讀取也可以寫入),第三個參數是PixelFormat的列舉值,Format32bppArgb代表32位元的影像格式(包含Alpha、R、G、B的色彩通道)。

unsafe是C#要使用非安全性代碼時必須使用的關鍵字,此部分先空著,等一下會在這個括號裡加東西。
UnlockBits是用來解鎖LockBits鎖住的記憶體,這是必要的東做,不做的話有可能會出現難以預期的錯誤。
最後將MyMyNewBmp顯示在pictureBox上

註:如果無法使用unsafe的話,請對專案按右鍵/屬性,點選建置/勾選允許不安全的程式碼。



接著我們開始製作C++函式來給C#調用。

對方案按下右鍵/加入/新增專案

嘗試尋找一個C++的CLR類別庫專案。
註:如果找不到的話請見下一張圖。

如果沒有找到的話就必須先安裝CLI的套件。


新增完成後,進入C++的程式碼編輯器中。


接著在類別中新增以下函式:

public:
void inline colorTo255(unsigned char* ptr, int width, int height, int channel)
{
unsigned char** fp = new unsigned char* [height];
int Stride = width * channel, x = 0, y = 0;
for (int j = 0; j < height; j++)
fp[j] = ptr + (Stride * j);
for (y = 0; y < height; y++)
{
for (x = 0; x < Stride; x += channel)
{
fp[y][x] = 255 - fp[y][x];
fp[y][x + 1] = 255 - fp[y][x + 1];
fp[y][x + 2] = 255 - fp[y][x + 2];
}
}
delete[] fp;
}

解說:
我們會在稍後引入點陣圖第0個像素的指標(ptr),其他參數包括width(影像寬度)、height(影像高度)、channel(通道數,有ARGB四種,所以此範例中的值應為四)
Stride是指掃描寬度(影像的每一列有多少位元組),x、y為等一下會用到的座標。
在 for (int j = 0; j < height; j++)fp[j] = ptr + (Stride * j); 中,我們將fp設定為影像中每一列開頭的指標位置的指標。
在外層迴圈,為逐列掃描,一個影像有幾列高度(height)就有多高。
在內層迴圈,為逐行掃描,每次增加4個位元組。
fp[y][x] = 255 - fp[y][x]; 將影像中的(x,y)座標的B值反轉。
fp[y][x + 1] = 255 - fp[y][x + 1];將影像中的(x,y)座標的G值反轉。
fp[y][x + 2] = 255 - fp[y][x + 2];將影像中的(x,y)座標的R值反轉。
註:在這裡,色彩空間的排列方式為BGRA(意即fp[y][x + 3]會是Alpha值)
結束後,使用 delete[] fp; 刪除不再需要用到的fp


 然後我們對這個C++專案按右鍵/建置。

接著對C#專案按右鍵/加入/參考。


選擇剛剛建立的C++專案,按下確定。

加入完參考後就可以讓C#呼叫剛剛建立的類別了,我們回到C#的程式碼編輯器中,在表頭加入剛剛的名稱空間,並新增C++物件。

using ClassLibrary2;
註:意思就是引入剛剛在C++程式碼中出現的「namespace ClassLibrary2」

接著回到處理影像的那部分(unsafe那裡),輸入以下程式碼:

private void 處理ToolStripMenuItem_Click(object sender, EventArgs e)
{
      Bitmap MyNewBmp = bitmap;
      Rectangle MyRec = new Rectangle(0, 0, MyNewBmp.Width, MyNewBmp.Height);
      BitmapData MyBmpData = MyNewBmp.LockBits(MyRec, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
       unsafe
       {
            C.colorTo255((byte*)MyBmpData.Scan0, MyNewBmp.Width, MyNewBmp.Height, 4);
        }
        MyNewBmp.UnlockBits(MyBmpData);
        pictureBox1.Image = MyNewBmp;
}

解說:
colorTo255是剛剛建立的C++函式,MyBmpData.Scan0是MyBmpData的第0個像素(我們將它轉換成byte*),MyNewBmp.Width是影像寬度,MyNewBmp.Height是影像高度 ,4是指通道數(包含ARGB四個通道)。


最後可以來測試程式是否正確了,我們按照剛剛的步驟執行程式,試著載入圖片。



然後按下選單中的處理。


如果看到影像的色彩被反轉了,恭喜你,成功了。

=====分隔線=====
如果覺得這篇文有幫助到你們的話,請留言或幫忙按個廣告吧。

您的支持是我寫文的最大動力。

如果依然失敗了,也請留言讓我知道,看看哪個環節出了問題,我有看到就會回。


希望這篇文有幫助到各位ξ( ✿>◡❛)

2019年6月2日 星期日

使用C#呼叫C++類別庫

本篇將介紹如何使用C#呼叫C++的類別並使用。

我們以C# Window Forms App作為範例。


新增Windows Form專案後,對方案按下右鍵/加入/新增專案

嘗試尋找一個C++的CLR類別庫專案。
註:如果找不到的話請見下一張圖。

如果沒有找到的話就必須先安裝CLI的套件。

新增完成後,進入C++的程式碼編輯器中。
我們在Class1類別中新增add()函數:
namespace ClassLibrary1 {
public ref class Class1
{
public:
int add(int number1, int number2)
{
return number1 + number2;
}
};
}
註:這個函數將回傳兩個參數加起來後的值。

接著對此專案按下右鍵/建置

建置完成後對著C#的專案按下右鍵/加入/參考

選擇剛剛建立的C++專案,按下確定。

加入完參考後就可以讓C#呼叫剛剛建立的類別了,我們進入最初建立的Windows Form表,對著表單點兩下進入C#的程式碼編輯器。

然後在表頭加入剛剛的名稱空間:
using ClassLibrary1;
註:意思就是引入剛剛在C++程式碼中出現的「namespace ClassLibrary1」

接著在Form1_Load中新增剛剛建立的Class1類別:
 Class1 c = new Class1();

然後使用Class1將自己定義的5跟10這兩個數字加起來:
int number = c.add(5, 10);

最後使用MessageBox顯示這個數字:
MessageBox.Show("" + number);

註:因為C#的字串類別的運算子有重載過,在字串後使用加號會自動將之後的型別轉成字串,所以我們用引號+數字就可以正常顯示該數字,並不需要大費周章的使用:
MessageBox.Show(Convert.ToString(number));


輸入完成之後按下執行:
如果看到剛才輸入的兩個數字加起來後的值,恭喜你,呼叫成功了。

=====分隔線=====
如果覺得這篇文有幫助到你們的話,請留言或幫忙按個廣告吧。

您的支持是我寫文的最大動力。

如果依然失敗了,也請留言讓我知道,看看哪個環節出了問題,我有看到就會回。


希望這篇文有幫助到各位ξ( ✿>◡❛)

2019年5月6日 星期一

使用Keras卷積神經網路辨別肺部醫學影像(四)

上一章「使用Keras卷積神經網路辨別肺部醫學影像(三)」

在上一章,我們得到了一個.npy檔,裡面裝著4500張男性影像和4500張女性影像。
在這一章,我們將建立訓練模型,並輸出成.h5的檔案 


在開始之前,需要理解下列名詞:
  • 卷積層
  • 池化層
  • Onehot encoding
  • 平坦層
  • 隱藏層
  • 輸出層
  • 活化函數
  • 損失函數

CNN卷積神經網路可分為兩大部分:
  • 影像的特徵提取:卷積層、池化層
  • 完全連結神經網路:平坦層、隱藏層、輸出層


關於卷積運算: 
卷積層的意義是將一個影像進行卷積運算,產生多個影像。 我們在進行影像操作時會使用到濾鏡,不同的濾鏡套用到影像中會產生不同的影像,可幫助程式提取不同的特徵。 

關於縮減取樣:
 池化層中的Max-Pool運算可將影像進行縮減取樣,例如一張14x14的影像,進行Max-Pool運算後,縮小為7x7的影像。  

關於Onehot encoding: 經過Onehot encoding轉換,可幫助程式進行分類。 
例如數字3,可轉換為[0,0,0,1,0,0,0,0,0,0] 
例如數字8,可轉換為[0,0,0,0,0,0,0,0,1,0] 

關於神經網路: 
有個公式是:輸出=活化函數(輸入X權重X偏差) 
例如現有250個輸入神經元,每個神經元乘上一個權重並加上偏差之後,可以傳遞到第二層(隱藏層)。 
在隱藏層又乘上權重並加上偏差之後,可將結果傳達到第三層(輸出層或第二層隱藏層)。

關於活化函數:
當神經元接收刺激時(輸入X權重+輸出)經過活化函數的運算,大於臨界值會傳遞到下一個神經元。

關於損失函數:

損失函數用以計算誤差,從錯誤中學習。


建立一個train_4500x4500的python檔案,程式碼如下:


import array import sys 
import numpy as np 
import cv2 
import glob 
import random from keras.utils 
import np_utils from keras.models 
import Sequential from keras.layers 
import Dense,Dropout,Activation,Flatten from keras.layers 
import Conv2D,MaxPooling2D,ZeroPadding2D 

#設定亂數種子,讓每次訓練產生的結果相同 np.random.seed(0) 

#產生性別標籤清單 
labellist=[] 

#設定影像寬度、高度 
imgwidth=128 imgheight=128 
#將上次產生的.npy檔載入至名為x_img_train的Array中 x_img_train=np.load("size128_F4500_M4500.npy") 
#設定前4500個影像標籤為女性(用數字1表示) 
for i in range(0,4500): 
(四個空格)labellist.append(1) 
#設定後4500個影像標籤為男性(用數字0表示) 
for i in range(4500,9000):
(四個空格)labellist.append(0) 
#將性別標籤轉換成Array 
y_label_train=np.array(labellist, dtype=np.int32) 
#將x_img_train轉換成(9000x128x128x1)的四維陣列 x_img_train=np.reshape(x_img_train,9000,imgwidth,imgheight,1))
#將性別標籤進行Onehot encoding轉換 y_label_train_OneHot=np_utils.to_categorical(y_label_train) 
#建立模型 
model=Sequential() 
#建立卷積層1 
model.add(Conv2D(filters=32,kernel_size=(5,5),input_shape=(imgwidth,imgheight,1),activation='relu',padding='same')) 
#隨機釋放神經元 
model.add(Dropout(rate=0.3)) 
#建立池化層1 
model.add(MaxPooling2D(pool_size=(2,2)))
#建立卷積層2 
model.add(Conv2D(filters=64,kernel_size=(5,5),activation='relu',padding='same')) 
#隨機釋放神經元 
model.add(Dropout(0.3)) 
#建立池化層2 
model.add(MaxPooling2D(pool_size=(2,2))) 
#建立卷積層3 
model.add(Conv2D(filters=128,kernel_size=(5,5),activation='relu',padding='same')) 
#隨機釋放神經元 
model.add(Dropout(0.5)) 
#建立池化層3 
model.add(MaxPooling2D(pool_size=(2,2))) 
#建立平坦層 
model.add(Flatten()) 
#隨機釋放神經元 
model.add(Dropout(rate=0.25)) 
#建立隱藏層 
model.add(Dense(512,activation='relu')) 
#隨機釋放神經元 
model.add(Dropout(rate=0.4)) 
#建立輸出層 
model.add(Dense(2,activation='softmax')) 
#定義訓練方式 model.compile(loss='categorical_crossentropy',optimizer='adam',metrics=['accuracy']) 
#設定訓練用的函數 
def training(times):
(四個空格)train_history=model.fit(x_img_train,y_label_train_OneHot,shuffle=True,epochs=times,batch_size=128,verbose=2) 
#如未完成訓練,可載入上次產生的訓練檔接續訓練
from keras.models import load_model #model.load_weights("Fiborsis_sex.h5")
#訓練1回,每回訓練20次 
for k in range(1): 
(四個空格)print('訓練第:',k,'次') 
training(20)
#輸出權重 
model.save_weights("Fiborsis_sex.h5")
#輸出模型檔 
model.save("Fiborsis_sex_result.h5")


程式碼解說: 我們一開始建立影像清單(labellist),並按照上次產生的size128_F4500_M4500.npy檔案中的性別順序填入標籤(女性為1男性為0)。  
for i in range(0,4500):
    labellist.append(1) 
for i in range(4500,9000):
    labellist.append(0) 

接著對資料進行前置處理。
y_label_train=np.array(labellist, dtype=np.int32) x_img_train=np.reshape(x_img_train,9000,imgwidth,imgheight,1)) y_label_train_OneHot=np_utils.to_categorical(y_label_train) 

建立卷積層,讓程式隨機產生32個濾鏡,每個濾鏡為5x5的大小,輸入的形狀為(寬度,長度,1),活化函數為relu 。
model.add(Conv2D(filters=32,kernel_size=(5,5),input_shape=(imgwidth,imgheight,1),activation='relu',padding='same')) 

為了防止過度擬合的現象發生,隨機放棄30%的神經元。
 model.add(Dropout(rate=0.3)) 

將圖片縮小,進行縮減取樣。 
model.add(MaxPooling2D(pool_size=(2,2)))  

再次建立卷積層,共產生64個濾鏡,每個濾鏡為5x5的大小,輸入的形狀為(寬度,長度,1),活化函數為relu 。
model.add(Conv2D(filters=64,kernel_size=(5,5),activation='relu',padding='same')) 

隨機放棄30%的神經元
 model.add(Dropout(rate=0.3)) 

進行縮減取樣(然後依此類推)。 
model.add(MaxPooling2D(pool_size=(2,2)))  

輸出結果,共有兩種結果 。
model.add(Dense(2,activation='softmax')) 

執行訓練,輸入x_img_train,引入y_label_train_OneHot做為參考解答。
啟動shuffle將資料順序打亂。 epochs代表進行幾次訓練周期,batch_size為每一批次共有幾筆資料,verbose=2代表顯示訓練過程。
train_history=model.fit(x_img_train,y_label_train_OneHot,shuffle=True,epochs=times,batch_size=128,verbose=2)

將訓練到一半的model儲存起來,下次可接續訓練(Fiborsis_sex.h5為檔名)
model.save_weights("Fiborsis_sex.h5")

載入上次儲存的模行檔(Fiborsis_sex.h5為檔名)
model.load_weights("Fiborsis_sex.h5")

輸出模型檔 (Fiborsis_sex_result.h5為檔名)
model.save("Fiborsis_sex_result.h5")


在下一個章節,我們將執行訓練檔,並驗證結果。

下一章「使用Keras卷積神經網路辨別肺部醫學影像(五)」

2019年4月22日 星期一

使用Keras卷積神經網路辨別肺部醫學影像(三)

上一章「使用Keras卷積神經網路辨別肺部醫學影像(二)」

由上一個步驟已經得到了兩個分別裝著男性肺部影像與女性肺部影像的資料夾。


接著我們將把這些影像壓縮成一個.npy檔。

至於為什麼要做壓縮呢,因為後續進行訓練的時候,如果是一張一張影像慢慢讀進程式,會拖累執行的效能。而且比起一個檔案,數千張的影像檔在攜帶上也不方便,全部壓縮在一個檔案是很方便的。

現在,我們建立一個python程式,命名為small128.py

程式碼及註解如下:
import array
import numpy as np
import cv2
import glob
#建立影像清單
imglist=[]
#計數器
filecount=0
#設定圖片寬度
imgwidth=128
#設定圖片高度
imgheight=128
#迭代女性資料夾中的影像
for filestr in glob.glob('image_F/*.png'):
    #利用opencv讀取影像檔
    #參數中的IMREAD_GRAYSCALE代表是以灰階模式讀取而非全彩
    img = cv2.imread(filestr,cv2.IMREAD_GRAYSCALE)
    #將影像縮放為128x128的大小
    img=cv2.resize(img,(imgwidth,imgheight))
    #將每個像素的值由0-255的整數轉換為0-1的浮點數
    img=img/255.0
    #將處理過後的影像放入影像清單中
    imglist.append(img)
    #計算目前處理了幾張影像
    filecount=filecount+1
    #進度條,查看目前程式跑到哪了
    if filecount%100==0:
        print(filecount,end=' ')
    #設定只放入4500張影像
    if filecount>=4500:
        break
#迭代男性資料夾中的影像
for filestr in glob.glob('image_M/*.png'):
    #利用opencv讀取影像檔
    #參數中的IMREAD_GRAYSCALE代表是以灰階模式讀取而非全彩
    img = cv2.imread(filestr,cv2.IMREAD_GRAYSCALE)
    #將影像縮放為128x128的大小
    img=cv2.resize(img,(imgwidth,imgheight))
    #將每個像素的值由0-255的整數轉換為0-1的浮點數
    img=img/255.0
    #將處理過後的影像放入影像清單中
    imglist.append(img)
    #計算目前處理了幾張影像
    filecount=filecount+1
    #進度條,查看目前程式跑到哪了
    if filecount%100==0:
        print(filecount,end=' ')
    #設定只放入4500張影像(加上女性的影像共9000張)
    if filecount>=9000:
        break
#將影像清單轉換成Array
img_array=np.array(imglist)
#儲存影像
np.save("size128_F4500_M4500.npy",img_array)

我們一開始建立影像清單,並設定好影像的大小(影像越大,訓練時就需要消耗更多的資源,所以建議縮小影像),我們將影像統一縮放為長128px、寬128px的大小
imglist=[]
imgwidth=128
imgheight=128

接著我們利用OpenCV讀取影像(需import cv2)。
img = cv2.imread(filestr,cv2.IMREAD_GRAYSCALE)
讀取時我們將原本的彩色影像轉換成灰階影像(全彩的影像每個像素需佔用24位元的大小,灰階的影像只需佔用8個位元),此動作並不會降低訓練的準確度,因為這些肺部影像本來就是黑白的。

接著我們使用OpenCV的resize函數,將影像縮放為一開始設定好的128x128的大小,此部分不再多加贅述。
img=cv2.resize(img,(imgwidth,imgheight))

接著我們將每個像素的值標準化(將介於0~255的整數標準化為0~1.0的浮點數),這個動作可以提高訓練的準確率。
img=img/255.0

然後將處理完的影像依序加入影像清單中。
imglist.append(img)

接著將影像清單轉換成訓練時需要的格式。
img_array=np.array(imglist)

最後,將這些影像儲存在一個size128_F4500_M4500.npy的檔案中。
np.save("size128_F4500_M4500.npy",img_array)

執行後,會得到一個npy檔,裡面裝著4500張男性影像和4500張女性影像。

在下一個章節,我們將建立訓練模型,開始建構這個計畫的核心程式。

2019年4月20日 星期六

CSS選擇器的權重大小一覽

閱讀這篇文章前   請先具備

CSS Selectors

實際撰寫與嘗試的經驗

在開發時,常常會發現給標籤添加樣式,卻沒效果的這種情況

這種情況分成兩種
1.選擇器寫錯、選錯標籤
2.選對了、但被其他的樣式覆蓋掉了

CSS的樣式被覆蓋的現象、就是權重問題

對權重問題理解不夠深的話,很容易就進入一段時間沒有產出的情況

#被覆蓋的樣式可透過F12看到,他們是被槓掉的

那麼來寫一下範例

body{
    margin: 0;
}
選到了body標籤,此時的權重假設為1、代表他能幹掉權重小於1的樣式

此時打開F12,會發現body{margin: 8px;}被幹掉了,這個樣式是chrome瀏覽器的預設樣式
也就是說body本身就有預設的margin,這個樣式被稱為預設樣式,權重為0

接著
body{
    margin: 0;
}
body{
    margin: 5px;
}

此時,body的margin為5px
在相同權重下,後面添加的樣式會幹掉前面的,我們假設他為1.1

再來
body{
    margin: 0;
}
body{
    margin: 5px;
}

html  body{
    margin: 10px;
}

此時,body的margin為10px
標籤是有疊加權重的,選到html底下的body,也就是說途中標籤選取器經過(但不選取)html,選到底下的body,此時的權重會是2,也就是1+1,但很重要的事情是,不管這個權重怎麼加,也不會超過class或id這兩種選取器

基礎觀念都有了、那接下來就一併列出吧

body{//1

}
body{//1.1

}
html body{//2

}
html body{//2.1,因為後寫的關係,所以增加0.1

}
.body{//10,你沒看錯,就是10,但標籤選取器的權重怎麼疊加都幹不掉這個10

}
html .body{//1+10=11

}
.html .body{//10+10=20

}
.html .body{//10+10=20,與上方權重相同,因為後寫的關係,所以為20.1

}
#body{//100

}
#html .body{//100+10=110

}
.html #body{//10+100=110,與上方權重相同,因為後寫的關係,所以為110.1,如果後寫的是#html .body,那麼#html .body就能幹掉.html #body

}
#html #body{//100+100=200

}

#html #div div .body{//100+100+1+10=211

}

行內樣式//權重1000
<body margin='20px'></body>

body{
    margin: 0px !important;//這樣寫,權重10000
}

2019年4月19日 星期五

使用Keras卷積神經網路辨別肺部醫學影像(二)


本例將辨識肺部X光照片所屬的性別,會利用一個資料檔(.csv)和十二個包含影像檔(.png)的資料夾。


使用Excel開啟附屬的csv檔,查看各張影像的訊息。從資料中可以取出圖片的檔名,疾病、年齡、性別等資料。


從影像資料夾中,可找到資料表中各個影像檔的檔名。

首先,要將資料檔進行分類,先建立兩個空的資料夾,命名為
「image_F」(女性)「image_M」(男性)


接著建立image_M.py檔,將男性影像歸類到指定資料夾。

程式碼及註解如下:

import csv
import os
import glob
from shutil import copyfile
# 開啟 CSV 檔案
with open('Data_Entry_2017.csv', newline='') as csvfile:
    # 讀取 CSV 檔案內容
    rows = csv.DictReader(csvfile)
    #計數器
    count=0
    # 以迴圈輸出每一列
    for row in rows:
        if row['Patient Gender']=='M':#如果性別欄位的值是M
            src='images/'+row['Image Index']#取得影像路徑
            dst='image_M/'+row['Image Index']#目標影像路徑
            if  os.path.exists(src):#如果檔案存在
                    copyfile(src, dst)#將影像複製到目標資料夾   
                    count=count+1#計錄目前的進度
                    if count%100==0:#顯示進度
                        print(count)

接著執行程式,過程中會顯示進度。



接著複製程式碼到image_F.py檔。


注意反白處,要將F更改為M:

import csv
import os
import glob
from shutil import copyfile
# 開啟 CSV 檔案
with open('Data_Entry_2017.csv', newline='') as csvfile:
    # 讀取 CSV 檔案內容
    rows = csv.DictReader(csvfile)
    #計數器
    count=0
    # 以迴圈輸出每一列
    for row in rows:
        if row['Patient Gender']=='F':#如果性別欄位的值是M
            src='images/'+row['Image Index']#取得影像路徑
            dst='image_F/'+row['Image Index']#目標影像路徑
            if  os.path.exists(src):#如果檔案存在
                    copyfile(src, dst)#將影像複製到目標資料夾   
                    count=count+1#計錄目前的進度
                    if count%100==0:#顯示進度
                        print(count)

接著執行後,得到了兩個分別裝著男性肺部影像與女性肺部影像的資料夾。
下一回將示範如何將數千個影像檔打包為一個資料檔。
下一章「使用Keras卷積神經網路辨別肺部醫學影像(三)」

使用Keras卷積神經網路辨別肺部醫學影像(一)

本系列文章將使用Keras卷積神經網路(CNN)辨別肺部醫學影像。

在開始之前,需要額外準備以下Python套件:
  • numpy
  • opencv
  • keras

同時亦需準備一定數量的影像檔(.jpeg、.png)與資料檔(.csv):



本範例所採用的檔案來自於這個網站:

https://nihcc.app.box.com/v/ChestXray-NIHCC

我們先從簡單的範例開始,下一篇將以辨識肺部X光照片所屬的性別為例。

下一章「使用Keras卷積神經網路辨別肺部醫學影像(二)」

2019年4月4日 星期四

標籤的秘密

上一回介紹的是h1~h6的標籤

h開頭的標籤有著標題的意思,也能達到改變文字大小與粗細的效果

標籤的秘密就是在於它有著一體雙效的力量,"意思"與"造型"

絕大多數的標籤都具有意思,比方說strong標籤的意思是重點強調,p標籤的意思是段落

造型方面,前者能讓文字變得更粗,後者能讓畫面變寬敞變好看

但是實際做網站的時候呢,造型方面會由CSS統一負責,而html只要負責意思的部分就好了。

所以說,不要為了讓畫面變寬敞而使用p標籤,不要為了讓文字變粗而使用strong標籤,不要為了讓字變大而使用h1標籤,而是要依照意義使用。
之後再統一由CSS來做造型的修改

這樣才能兼顧到網站的SEO與視覺畫面,行銷和設計師才不會哭哭喔

html標籤介紹 h1到h6

h1~h6的標籤

h1的標籤字體很大很粗
h2的字體比h1小一點  也比較細一點
h6的字體最小最細

h1是長這樣的

hi我是h1

h6是長這樣的
hi我是h6
h1的意義是網站主標題
這個標籤在網站內的權重是非常大的
搜尋引擎在抓網站內容的時候會被這個標籤吸取注意力
所以不要重複使用h1標籤
這樣搜尋引擎會不知道這個網站的哪裡是重點
用法:h1 整個網站一個 h2:每個頁面一個 h3:每個頁面多個

2019年3月30日 星期六

[小技巧]懸置else問題

懸置else問題(dangling-else problem)是一種使用if...else時可能會出現的錯誤。

以下以JavaScript為例:


以剛入門的初學者來說,可能會認為這段程式碼會印出"outer"。

但是實際執行結果並非如此。


實際上,電腦對這段程式的解讀會是這個樣子:



所以這段程式並不會印出任何文字。

解決辦法:


為了避免類似錯誤發生,建議替if...else加上大括弧,解決懸置else的問題。

2019年3月26日 星期二

[小技巧]cin和cout代表的意義

初學者在學C++的時候,有時可能會將cin和cout弄混,這往往是因為沒有了解這兩個名稱的意義。
其實cin和cout並非是單一一個單字,而是c加上一個單字所組成,像cin讀作「see-in」,而cout讀作「see-out」,這樣就可以看出是in(輸入)和out(輸出)的區別。
下次再弄混這兩個名稱時,請不要用死背的方式記住它們,不妨去理解該單字的意義,如此一來便能更方便的記住這些名稱。

2019年2月7日 星期四

[資料結構]關於陣列效能的改善

本篇文章探討陣列元素讀取的順序對效能的影響。

首先我們來看看這段用C++編寫的程式碼


解析:
我們在第5到第8行利用指標建構了一個8192*8192的陣列,隨後在第10行做起動計時器的動作。

在第11到第13行,我們利用迴圈將陣列填滿0。

在第14行,我們停止計時器,計算填滿陣列所花費的時間。

執行結果如下

此次填滿陣列花費了136毫秒


接著,我們對調行與列的位置,然後再執行一次程式

執行結果如下


此次填滿陣列花費了823毫秒,比上一次多花費了687毫秒

同樣都是填滿陣列的動作,為什麼會有如此可觀的時間差距呢。

以第一支程式來看,填滿陣列的動作是由左至右執行。


但是,第二支程式將行列的位置對調了,執行的順序變成由上至下執行。


這樣會造成什麼影響呢,從程式碼的第5到第8行中,可看出我們創造出的是以列為主的陣列。

在第一支程式中,一次填滿一列,填完一列再填滿下一列,只需花費136毫秒。

但是在第二支程式中,每填完一個元素,指標位置就得挪到下一列,直到填完整行元素後,指標位置才回到第一列的位置,浪費了不少移動時間。




2019年1月20日 星期日

[小技巧]如何利用小畫家來打馬賽克

小畫家也可以用來打馬賽克?

是的,真的可以。

首先,開啟小畫家並載入圖片


然後,框選出想要打馬賽克的位置


接著,按上方的調整大小,將比例縮成20%的大小,然後按確定



回到畫布上,原先框選的部分已經縮小了


重複上一個動作,將剛剛框選的部分放大為500%



























放大為原本的大小之後,馬賽克效果就完成了


2019年1月17日 星期四

[小知識]快閃記憶體

快閃記憶體會將電子訊號傳到媒體上,電子會被內部二氧化矽的小孔補捉住而改變電子電路的狀態,這些小孔在沒有外部電源的情況下能將捕捉到的電子保留多年。