訂閱
糾錯
加入自媒體

4種在生產(chǎn)中擾亂計算機視覺模型的方法

簡介

本文不是關于模型的質量。它甚至不涉及擴展、負載平衡和其他DevOp。它是關于一個更普遍但有時會遺漏的事情:處理不可預測的用戶輸入。

在訓練模型時,數(shù)據(jù)科學家?guī)缀蹩偸怯幸粋受控的數(shù)據(jù)環(huán)境。這意味著使用已經(jīng)準備好的數(shù)據(jù)集,或者有時間和資源手動收集、合并、清理和檢查數(shù)據(jù)。通過準確地執(zhí)行此操作,可以提高基于這些數(shù)據(jù)訓練的模型的質量。

假設模型足夠好,可以部署到生產(chǎn)中。在最好的情況下,仍然可以控制環(huán)境(經(jīng)典的服務器端部署)。但即便如此,用戶也會從各種設備和來源上傳圖像。在邊緣部署的情況下,還有一層復雜性:無法控制環(huán)境。

在這兩種情況下,模型都應該立即響應,開發(fā)人員沒有時間手動檢查數(shù)據(jù)。因此,至關重要的是:

· 隨時準備數(shù)據(jù)

· 盡可能接近訓練時間進行預處理

否則,實際生產(chǎn)模型的質量可能會比預期的低很多。這種情況之所以發(fā)生,是因為人們在數(shù)據(jù)中引入了偏見,而這是模型所沒有預料到的。

真實案例

在接下來的內容中,將介紹一家公司開發(fā)計算機視覺模型時遇到的4個問題。在將其傳遞給算法(主要是神經(jīng)網(wǎng)絡),以下部分都與圖像處理有關:

· 存儲在EXIF中的方向

· 非標準顏色配置文件

· 圖像庫中的差異

· 調整算法

對于每一個項目,都會提供一個案例研究和一段代碼,在生產(chǎn)中解決這個問題。

存儲在EXIF中的方向

如今,人們使用移動相機拍照(約91%,而且這個數(shù)字還在增長)。

通常情況下,移動設備會以預先確定的固定方向存儲圖像,而不管拍照時相機的實際位置是什么;謴统跏挤较蛩璧男D角度作為元信息存儲在EXIF中。

在移動設備或現(xiàn)代桌面軟件上觀看此類圖像可能沒問題,因為它們可以處理這些EXIF信息。但是以編程方式加載圖像時,許多庫讀取原始像素數(shù)據(jù)并忽略元信息。這會導致錯誤的圖像方向。在下面的示例中,我使用了PIL,它是用于圖像處理的最流行的Python庫之一。

input_image = "data/cup.jpg"

image = PIL.Image.open(input_image)

np_image_initial = np.a(chǎn)rray(image)

向神經(jīng)網(wǎng)絡發(fā)送這樣的圖像可能會產(chǎn)生完全隨機的結果。要解決這個問題,還需要讀取EXIF信息,并相應地旋轉/鏡像圖像。

# get image EXIF

image_exif = image.getexif()

# retrieve orientation byte

orientation = image_exif.get(0x0112)

print(orientation)

# 6

# get the corresponding transformation

method = {

   2: PIL.Image.FLIP_LEFT_RIGHT,

   3: PIL.Image.ROTATE_180,

   4: PIL.Image.FLIP_TOP_BOTTOM,

   5: PIL.Image.TRANSPOSE,

   6: PIL.Image.ROTATE_270,

   7: PIL.Image.TRANSVERSE,

   8: PIL.Image.ROTATE_90,

}.get(orientation)


if method is not None:
   

  # replace original orientation

   image_exif[0x0112] = 1
   

   # apply transformation

   image = image.transpose(method)

np_image_correct = np.a(chǎn)rray(image)

PIL允許讀取EXIF元信息并對其進行解析。它知道在哪里尋找方向。存儲必要信息的字節(jié)是0x0112,并且文檔說明了如何處理每個值,以及如何處理圖像來恢復初始方向。

使用EXIF代碼讀取并正確旋轉的杯子圖像

這個問題似乎已經(jīng)解決了,對嗎?嗯,還沒有。

讓我們試試另一張照片。這一次,我將使用現(xiàn)代的HEIF圖像格式,而不是舊的JPEG(請注意,需要一個特殊的PIL插件)。默認情況下,iPhone以這種格式拍攝HDR圖片。

圖中顯示了站在門口的女孩的圖像及其元數(shù)據(jù)

一切看起來都一樣。讓我們試著用一種天真的方式從代碼中讀取它,而不看EXIF。

input_image = "data/person.heic"

image = PIL.Image.open(input_image)

np_image_initial = np.a(chǎn)rray(image)

圖像:

原始圖像方向正確!但EXIF建議將其逆時針旋轉90度!所以方位恢復代碼會失敗。

這是一個已知的問題,iPhone在HEIC拍攝時,由于某些不清楚的原因,iPhone存儲的原始像素是正確的,但在以這種格式拍照時,仍然在EXIF中保持傳感器方向。

因此,當圖像在iPhone上以HEIC格式拍攝時,應該修正算法并省略旋轉。

# get image EXIF

image_exif = image.getexif()

# get image format

image_format = image.format

# retrieve orientation byte

orientation = image_exif.get(0x0112)

print(orientation)

# 6

print(image_format)

# HEIF

# get the corresponding transformation

method = {

   2: PIL.Image.FLIP_LEFT_RIGHT,

   3: PIL.Image.ROTATE_180,

   4: PIL.Image.FLIP_TOP_BOTTOM,

   5: PIL.Image.TRANSPOSE,

   6: PIL.Image.ROTATE_270,

   7: PIL.Image.TRANSVERSE,

   8: PIL.Image.ROTATE_90,

}.get(orientation)


if method is not None:
   

  # replace original orientation

   image_exif[0x0112] = 1
   

   # apply transformation if not HEIF

   if image_format != "HEIF":

       image = image.transpmemoryviewe(method)

np_image_correct = np.a(chǎn)rray(image)

當然,來自其他設備的圖像定向可能會有更多問題,而這段代碼可能并不詳盡。但它清楚地表明了一個人應對用戶輸入的謹慎程度,以及即使沒有惡意意圖,用戶輸入的不可預測性。

非標準顏色配置文件

元信息隱藏了另一個挑戰(zhàn)。圖片可能會被拍攝并存儲在不同的顏色空間和顏色配置文件中。

有了顏色空間,它或多或少是清晰的。它定義了用于存儲每個像素的圖像顏色信息的通道。最常見的兩種顏色空間是RGB(紅-綠-藍)和CMYK(青-品紅-黃-黑)。人們幾乎不會弄錯CMYK中的圖像,因為它們有不同數(shù)量的通道:RGB是3。

因此,由于輸入通道數(shù)量錯誤,將其發(fā)送到網(wǎng)絡會立即中斷。所以這種從CMYK到RGB的轉換很少被忘記。

顏色配置文件要復雜得多。這是輸入(攝像頭)或輸出(顯示)設備的特征。它說明了特定于設備的記錄或顯示顏色的方式。

RGB顏色空間有很多不同的配置文件:sRGB、Adobe RGB、Display P3等。每個配置文件定義了自己的方式,將原始RGB值映射到人眼感知的真實顏色。

這導致了一個事實,即相同的原始RGB值可能意味著不同圖片中的不同顏色。要解決這個問題,需要仔細地將所有圖像的顏色配置文件轉換為一個選定的標準。

通常,它是一個sRGB配置文件,因為由于歷史原因,它是所有網(wǎng)站的默認顏色配置文件。

在iPhone上拍攝的照片通常有一個顯示P3顏色的配置文件。讓我們從代碼中讀取圖像,看看它在進行顏色配置文件轉換和不進行顏色配置文件轉換時是什么樣子。

input_image = "data/city.heic"

image_initial = PIL.Image.open(input_image)

# open default ICC profile which is sRGB

working_icc_profile = PIL.ImageCms.getOpenProfile(

   "data/SRGB.icc"



# read ICC profile from the image

image_icc_profile = PIL.ImageCms.getOpenProfile(

   io.BytesIO(image_initial.info["icc_profile"])



# convert an image to default ICC profile

image_converted = PIL.ImageCms.profileToProfile(

   im=image_initial,

   inputProfile=image_icc_profile,

   outputProfile=working_icc_profile,

   renderingIntent=PIL.ImageCms.INTENT_PERCEPTUAL,

   outputMode="RGB"

image_initial_np = np.a(chǎn)rray(image_initial)

image_converted_np = np.a(chǎn)rray(image_converted)

可以看到,通過轉換讀取的圖像更生動,更接近原始圖像。根據(jù)圖片和顏色配置,這種差異可能更大或更小。

發(fā)送到神經(jīng)網(wǎng)絡的顏色(即像素值)可能會影響最終質量并破壞預測。因此,恰當?shù)嘏c他們合作至關重要。

圖像庫中的差異

正確讀取圖像是非常重要的。但與此同時,我們應該盡快做到這一點。因為在云計算時代,時間就是金錢。此外,客戶不想等待太久。

這里的最終解決方案是切換到C/C++。有時,在產(chǎn)生式推理環(huán)境中,它可能完全有意義。但如果你想留在Python生態(tài)系統(tǒng)中,有很多選擇。每個圖像庫都有自己的功能和速度。

直到現(xiàn)在,我只使用了PIL模塊。為了進行比較,我選擇了另外兩個流行的庫:OpenCV和Scikit image。

讓我們看看每個庫讀取不同大小的JPEG圖像的速度。

def read_cv2(file):
   

   image_cv2 = cv2.cvtColor(

       cv2.imread(

           file, 

           cv2.IMREAD_UNCHANGED

       ), 

       cv2.COLOR_BGR2RGB

   )
   

   return image_cv2

def read_pil(file):
   

   image_pil = PIL.Image.open(file)
   

   _ = np.a(chǎn)rray(image_pil)
   

   return image_pil

def read_skimage(file):
   

   image_skimage = skimage.io.imread(file)
   

   return image_skimage

num_repeats = 10

# read 37 JPEG images of different sizes and measure time

times_read_cv2 = measure_read_times(read_cv2, num_repeats)

times_read_pil = measure_read_times(read_pil, num_repeats)

times_read_skimage = measure_read_times(read_skimage, num_repeats)

對于小圖像,幾乎沒有區(qū)別。但對于大型的,OpenCV的速度大約是PIL和Scikit圖像的1.5倍。根據(jù)圖像內容和格式(JPEG、PNG等),這種差異可能從1.4x到2.0x不等。但總的來說,OpenCV要快得多。

網(wǎng)絡上還有其他可靠的基準給出了大致相同的數(shù)字。對于圖像書寫來說,這種差異可能更為顯著:OpenCV的速度要快4到10倍。

另一個非常常見的操作是調整大小。人們幾乎總是在將圖像發(fā)送到神經(jīng)網(wǎng)絡之前調整圖像的大小。這就是OpenCV真正閃耀的地方。

times_resize_cv2 = measure_time(

   f=lambda: cv2.resize(image_cv2, (1000, 1000), interpolation=cv2.INTER_LINEAR),

   num=20,

times_resize_pil = measure_time(

   f=lambda: image_pil.resize((1000, 1000), resample=PIL.Image.LINEAR),

   num=20,

times_resize_skimage = measure_time(

   f=lambda: skimage.transform.resize(image_skimage, (1000, 1000)),

   num=20,


print(f"cv2:       {np.mean(times_resize_cv2):.3f}s")

print(f"pil:       {np.mean(times_resize_pil):.3f}s")

print(f"skimage:   {np.mean(times_resize_skimage):.3f}s")

# cv2:       0.006s

# pil:       0.135s

# skimage:   4.531s

在這里,我拍攝了一張7360x4100的圖像,并將其調整為1000x1000。OpenCV比PIL快22倍,比Scikit image快755倍!

選擇正確的庫可以節(jié)省大量時間。需要注意的是,相同的調整大小算法可能會在不同的實現(xiàn)中產(chǎn)生不同的結果:

image_cv2_resized = cv2.resize(

   image_cv2, 

   (1000, 1000), 

   interpolation=cv2.INTER_LINEAR

image_pil_resized = image_pil.resize(

   (1000, 1000), 

   resample=PIL.Image.LINEAR

print(

   "Original images are the same:                    ", 

   (image_cv2 == np.a(chǎn)rray(image_pil)).a(chǎn)ll()

print(

   "Resized images are the same:                     ", 

   (image_cv2_resized == np.a(chǎn)rray(image_pil_resized)).a(chǎn)ll()

mae = np.a(chǎn)bs(image_cv2_resized.a(chǎn)stype(int) - 

            np.a(chǎn)rray(image_pil_resized, dtype=int)).mean()

print(

   "Mean Absolute Difference between resized images: ",

   f"{mae:.2f}"

# Original images are the same:                     True

# Resized images are the same:                      False

# Mean Absolute Difference between resized images:  5.37

在這里,人們可以注意到,我對OpenCV和PIL都使用線性插值進行下采樣。原始圖像是相同的。但結果不同。差別非常顯著:每種像素顏色平均255個像素中有5個不同。

因此,如果在訓練和推理過程中使用不同的庫來調整大小,可能會影響模型的質量。所以我們應該密切關注它。

調整算法

除了不同庫之間的速度差異外,即使在一個庫中,也有不同的調整大小算法。我們應該選擇哪一個?至少,這取決于是否要減小圖像大。ㄏ虏蓸樱┗蛟黾訄D像大小(上采樣)。

有許多調整圖像大小的算法。它們產(chǎn)生的圖像質量和速度不同。我只看這5個,它們足夠好、足夠快,并且在主流庫中得到支持。

下面提供的結果也與一些指南和示例一致。

image = cv2.cvtColor(

   cv2.imread(

       input_image, 

       cv2.IMREAD_UNCHANGED

   ), 

   cv2.COLOR_BGR2RGB


# define list of algorithms

algos = {

   "AREA": cv2.INTER_AREA,

   "NEAREST": cv2.INTER_NEAREST,

   "LINEAR": cv2.INTER_LINEAR,

   "CUBIC": cv2.INTER_CUBIC,

   "LANCZOS": cv2.INTER_LANCZOS4,


# draw original image

plt.imshow(image[1500:2500, 5000:6000])


for algo in algos:
   

  # downsample the original image with selected algorithm

   image_resized = cv2.resize(image, (736, 410), interpolation=algos[algo])
   

   # draw results

   plt.imshow(image_resized[150:250, 500:600])

對于下采樣,“area”算法看起來最好。它產(chǎn)生的噪音和偽影最少。事實上,它是OpenCV進行下采樣的首選。

現(xiàn)在讓我們進行上采樣——將采樣后的圖像恢復到原始大小,看看在這種情況下哪種算法最有效。

# create downsampled image with preferred "area" algorithm

image_downsampled = cv2.resize(image, (736, 410), interpolation=cv2.INTER_AREA)


# draw original image

plt.imshow(image[1500:2500, 5000:6000])

for algo in algos:
   

  # upsample the downsampled image back to the original size

   # with the selected resizing algorithm

   image_resized = cv2.resize(

       image_downsampled, 

       (7360, 4100), 

       interpolation=algos[algo]

   )
   

   # draw results

   plt.imshow(image_resized[1500:2500, 5000:6000])

對于上采樣,算法會產(chǎn)生更一致的結果。盡管如此,“cubic”插值看起來模糊程度最低,最接近原始(“l(fā)anczos”提供了類似的結果,但速度要慢得多)。

所以這里的最終結論是使用“area”插值進行下采樣,使用“cubic”算法進行上采樣。

請注意,正確的調整算法選擇在訓練期間也很重要,因為它可以提高整體圖像質量。更重要的是,訓練和推理階段的調整算法應該是相同的,否則模型可能會出問題。

結論

這篇文章,描述了在計算機視覺領域工作多年期間多次遇到的真實案例和問題。如果處理不當,它們中的每一個都可能會顯著降低生產(chǎn)中的模型質量。

感謝閱讀!

       原文標題 : 4種在生產(chǎn)中擾亂計算機視覺模型的方法

聲明: 本文由入駐維科號的作者撰寫,觀點僅代表作者本人,不代表OFweek立場。如有侵權或其他問題,請聯(lián)系舉報。

發(fā)表評論

0條評論,0人參與

請輸入評論內容...

請輸入評論/評論長度6~500個字

您提交的評論過于頻繁,請輸入驗證碼繼續(xù)

暫無評論

暫無評論

    掃碼關注公眾號
    OFweek人工智能網(wǎng)
    獲取更多精彩內容
    文章糾錯
    x
    *文字標題:
    *糾錯內容:
    聯(lián)系郵箱:
    *驗 證 碼:

    粵公網(wǎng)安備 44030502002758號