图像和像素(Images and Pixels)
圖像和像素
數(shù)字圖像只不過是數(shù)據(jù)——數(shù)字表示像素網(wǎng)格上特定位置的紅色、綠色和藍色的變化。大多數(shù)時候,我們將這些像素視為夾在計算機屏幕上的微型矩形。然而,通過一些創(chuàng)造性的思考和使用代碼對像素進行一些較低級別的操作,我們可以以多種方式顯示該信息。本教程致力于打破處理中的簡單形狀繪制,并使用圖像(及其像素)作為處理圖形的構建塊。
圖像入門。
希望您對數(shù)據(jù)類型的概念感到滿意。您可能經(jīng)常指定它們——浮點變量“速度”、整數(shù)“x”等。這些都是原始數(shù)據(jù)類型,位于計算機內存中的位可供我們使用。雖然可能有點棘手,但您希望也可以使用對象、存儲多條數(shù)據(jù)(以及功能)的復雜數(shù)據(jù)類型——例如,“Ball”類可能包括位置、大小和速度的浮點變量作為移動、顯示自身等的方法。
除了用戶定義的對象(例如 Ball)之外,Processing 還有一堆方便的類,無需我們編寫任何代碼即可使用。在本教程中,我們將研究PImage,這是一個用于加載和顯示圖像以及查看其像素的類。
PImage img; // Declare a variable of type PImagevoid setup() {size(320,240);// Make a new instance of a PImage by loading an image fileimg = loadImage("mysummervacation.jpg"); }void draw() {background(0);// Draw the image to the screen at coordinate (0,0)image(img,0,0); }使用PImage對象的實例與使用用戶定義的類沒有什么不同。首先,聲明了一個名為“img”的PImage類型的變量。其次,通過loadImage()方法創(chuàng)建PImage對象的新實例。loadImage()接受一個參數(shù),a指示文件名,并將該文件加載到內存中。loadImage()查找存儲在處理草圖的“數(shù)據(jù)”文件夾中的圖像文件。String
數(shù)據(jù)文件夾:我如何到達那里?
圖像可以通過以下方式自動添加到數(shù)據(jù)文件夾:
草圖→添加文件。. .
或手動:
草圖→顯示草圖文件夾
這將打開草圖文件夾。如果沒有數(shù)據(jù)目錄創(chuàng)建一個。否則,將您的圖像文件放入其中。
Processing 接受以下圖像文件格式:GIF、JPG、TGA、PNG。
在上面的示例中,我們從未調用“構造函數(shù)”來實例化PImage對象,這似乎有點奇怪,比如new PImage(). 畢竟,在大多數(shù)與對象相關的示例中,構造函數(shù)是生成對象實例所必需的。
Spaceship ss = new Spaceship(); Flower flr = new Flower(25);然而:
PImage img = loadImage("file.jpg");實際上,loadImage()函數(shù)執(zhí)行構造函數(shù)的工作,返回從指定文件名生成的PImage對象的全新實例。我們可以將其視為用于從文件加載圖像的PImage構造函數(shù)。為了創(chuàng)建空白圖像,使用createImage()函數(shù)。
// Create a blank image, 200 x 200 pixels with RGB color PImage img = createImage(200, 200,RGB);我們還應該注意,將圖像從硬盤加載到內存的過程是一個緩慢的過程,我們應該確保我們的程序只需要執(zhí)行一次,在setup(). 加載圖像draw()可能會導致性能下降以及“內存不足”錯誤。
加載圖像后,將使用image()函數(shù)顯示它。image()函數(shù)必須包含 3 個參數(shù)——要顯示的圖像、x 位置和 y 位置。可以選擇添加兩個參數(shù)來將圖像調整為特定的寬度和高度。
image(img,10,20,90,60);您的第一個圖像處理濾鏡
顯示圖像時,您可能希望更改其外觀。也許您希望圖像看起來更暗、透明、偏藍等。這種簡單的圖像過濾是通過 Processing 的tint()函數(shù)實現(xiàn)的。tint()本質上是 shape 的圖像等價物fill(),設置在屏幕上顯示圖像的顏色和 alpha 透明度。然而,圖像通常并不都是一種顏色。的參數(shù)tint()僅指定該圖像的每個像素使用多少給定顏色,以及這些像素應該顯示的透明程度。
對于以下示例,我們將假設已加載兩個圖像(向日葵和狗),并且將狗顯示為背景(這將允許我們展示透明度。)
PImage sunflower = loadImage("sunflower.jpg"); PImage dog = loadImage("dog.jpg"); background(dog);如果tint()接收一個參數(shù),則僅影響圖像的亮度。
// The image retains its original state. tint(255); image(sunflower,0,0); // The image appears darker. tint(100); image(sunflower,0,0);第二個參數(shù)將改變圖像的 alpha 透明度。
// The image is at 50% opacity. tint(255,127); image(sunflower,0,0);三個參數(shù)影響每種顏色的紅色、綠色和藍色分量的亮度。
// None of its red, most of its green, and all of its blue. tint(0,200,255); image(sunflower,0,0);最后,向該方法添加第四個參數(shù)來操作 alpha(與 2 相同)。tint()順便說一句,可以使用colorMode()指定值的范圍。
// The image is tinted red and transparent. tint(255,0,0,100); image(sunflower,0,0);像素、像素和更多像素
如果您剛剛開始使用 Processing,您可能會錯誤地認為唯一提供的用于繪制到屏幕的方法是通過函數(shù)調用?!霸谶@些點之間畫一條線”或“用紅色填充橢圓”或“加載此 JPG 圖像并將其放置在此處的屏幕上”。但不知何故,有人不得不編寫代碼,將這些函數(shù)調用轉換為設置屏幕上的各個像素以反映所請求的形狀。一條線沒有出現(xiàn)是因為我們說line(),它出現(xiàn)是因為我們?yōu)閮牲c之間的線性路徑上的所有像素著色。幸運的是,我們不必每天管理這種較低級別的像素設置。我們要感謝 Processing(和 Java)的開發(fā)人員,感謝他們處理這項業(yè)務的許多繪圖功能。
盡管如此,我們還是時不時想要打破我們世俗的圖形繪制存在,直接處理屏幕上的像素。處理通過像素陣列提供此功能。
我們熟悉屏幕上的每個像素在二維窗口中都有一個 X 和 Y 位置的想法。然而,陣列像素只有一維,以線性順序存儲顏色值。
舉個簡單的例子。該程序將窗口中的每個像素設置為隨機灰度值。像素數(shù)組就像其他數(shù)組一樣,唯一的區(qū)別是我們不必聲明它,因為它是一個處理內置變量。
示例:設置像素
size(200, 200); // Before we deal with pixels loadPixels(); // Loop through every pixel for (int i = 0; i < pixels.length; i++) {// Pick a random number, 0 to 255float rand = random(255);// Create a grayscale color based on random numbercolor c = color(rand);// Set pixel at that location to random colorpixels[i] = c; } // When we are finished dealing with pixels updatePixels();首先,我們應該在上面的例子中指出一些重要的東西。每當您訪問處理窗口的像素時,您必須提醒處理此活動。這是通過兩個功能完成的:
- loadPixels()這個函數(shù)在你訪問像素數(shù)組之前被調用,說“加載像素,我想和他們說話!”
- updatePixels()這個函數(shù)在你完成像素數(shù)組后調用,“繼續(xù)更新像素,我已經(jīng)完成了!”
在上面的例子中,因為顏色是隨機設置的,所以我們不必擔心像素在屏幕上的位置,因為我們只是設置了所有像素,而不考慮它們的相對位置。然而,在許多圖像處理應用中,像素本身的 XY 位置是至關重要的信息。一個簡單的示例可能是將每個偶數(shù)列像素設置為白色,將每個奇數(shù)列設置為黑色。你怎么能用一維像素陣列做到這一點?你怎么知道任何給定像素在哪一列或哪一行?
在使用像素進行編程時,我們需要能夠將每個像素視為生活在二維世界中,但要繼續(xù)訪問一個二維世界中的數(shù)據(jù)(因為這就是我們可以使用它的方式)。我們可以通過以下公式做到這一點:
這可能會讓您想起我們的二維數(shù)組教程。事實上,我們需要使用相同的嵌套 for 循環(huán)技術。不同之處在于,雖然我們想使用 for 循環(huán)來考慮二維像素,但當我們實際訪問像素時,它們存在于一維數(shù)組中,我們必須應用上圖中的公式。
讓我們看看它是如何完成的。
示例:根據(jù) 2D 位置設置像素
size(200, 200); loadPixels(); // Loop through every pixel column for (int x = 0; x < width; x++) {// Loop through every pixel rowfor (int y = 0; y < height; y++) {// Use the formula to find the 1D locationint loc = x + y * width;if (x % 2 == 0) { // If we are an even columnpixels[loc] = color(255);} else { // If we are an odd columnpixels[loc] = color(0);}} } updatePixels();圖像處理簡介
上一節(jié)介紹了根據(jù)任意計算設置像素值的示例。我們現(xiàn)在將看看如何根據(jù)現(xiàn)有PImage對象中的像素設置像素。這是一些偽代碼。
將圖像文件加載到 PImage 對象中
對于 PImage 中的每個像素,檢索像素的顏色并將顯示像素設置為該顏色。
PImage類包括一些有用的字段,用于存儲與圖像相關的數(shù)據(jù)——寬度、高度和像素。就像我們的用戶定義的類一樣,我們可以通過點語法訪問這些字段。
PImage img = createImage(320,240,RGB); // Make a PImage object
println(img.width); // Yields 320
println(img.height); // Yields 240
img.pixels[0] = color(255,0,0); // Sets the first pixel of the image to red
訪問這些字段使我們能夠遍歷圖像的所有像素并將它們顯示在屏幕上。
示例:顯示圖像的像素
PImage img;void setup() {size(200, 200);img = loadImage("sunflower.jpg"); }void draw() {loadPixels();// Since we are going to access the image's pixels tooimg.loadPixels();for (int y = 0; y < height; y++) {for (int x = 0; x < width; x++) {int loc = x + y*width;// The functions red(), green(), and blue() pull out the 3 color components from a pixel.float r = red(img.pixels[loc]);float g = green(img.pixels[loc]);float b = blue(img.pixels[loc]);// Image Processing would go here// If we were to change the RGB values, we would do it here,// before setting the pixel in the display window.// Set the display pixel to the image pixelpixels[loc] = color(r,g,b);}}updatePixels(); }現(xiàn)在,我們當然可以進行簡化以僅顯示圖像(例如,不需要嵌套循環(huán),更不用說使用image()函數(shù)將允許我們完全跳過所有這些像素工作。)但是,示例 15-7 提供了一個基本框架,用于根據(jù)每個像素的空間方向(XY 位置)獲取每個像素的紅色、綠色和藍色值;最終,這將使我們能夠開發(fā)更先進的圖像處理算法。
在我們繼續(xù)之前,我應該強調這個例子是有效的,因為顯示區(qū)域與源圖像具有相同的尺寸。如果不是這種情況,您只需計算兩個像素位置,一個用于源圖像,一個用于顯示區(qū)域。
int imageLoc = x + y*img.width; int displayLoc = x + y*width;我們的第二個圖像過濾器,制作我們自己的“色調”
就在幾段前,我們正在享受輕松的編碼會議,使用友好的tint()方法為圖像著色并添加 alpha 透明度。對于基本過濾,這種方法可以解決問題。然而,逐像素方法將允許我們開發(fā)自定義算法,以數(shù)學方式改變圖像的顏色。考慮亮度-較亮的顏色具有較高的紅色、綠色和藍色分量值。很自然地,我們可以通過增加或減少每個像素的顏色分量來改變圖像的亮度。在下一個示例中,我們根據(jù)鼠標的水平位置動態(tài)地增加或減少這些值。(注意,接下來的兩個示例僅包括圖像處理循環(huán)本身,其余代碼是假定的。)
示例:調整圖像亮度
for (int x = 0; x < img.width; x++) {for (int y = 0; y < img.height; y++ ) {// Calculate the 1D pixel locationint loc = x + y*img.width;// Get the R,G,B values from imagefloat r = red (img.pixels[loc]);float g = green (img.pixels[loc]);float b = blue (img.pixels[loc]);// Change brightness according to the mouse herefloat adjustBrightness = ((float) mouseX / width) * 8.0;r *= adjustBrightness;g *= adjustBrightness;b *= adjustBrightness;// Constrain RGB to between 0-255r = constrain(r,0,255);g = constrain(g,0,255);b = constrain(b,0,255);// Make a new color and set pixel in the windowcolor c = color(r,g,b);pixels[loc] = c;} }由于我們是在每個像素的基礎上更改圖像,因此不需要平等對待所有像素。例如,我們可以根據(jù)每個像素與鼠標的距離來改變每個像素的亮度。
示例:根據(jù)像素位置調整圖像亮度
for (int x = 0; x < img.width; x++) {for (int y = 0; y < img.height; y++ ) {// Calculate the 1D pixel locationint loc = x + y*img.width;// Get the R,G,B values from imagefloat r = red (img.pixels[loc]);float g = green (img.pixels[loc]);float b = blue (img.pixels[loc]);// Calculate an amount to change brightness// based on proximity to the mousefloat distance = dist(x,y,mouseX,mouseY);float adjustBrightness = (50-distance)/50;r *= adjustBrightness;g *= adjustBrightness;b *= adjustBrightness;// Constrain RGB to between 0-255r = constrain(r,0,255);g = constrain(g,0,255);b = constrain(b,0,255);// Make a new color and set pixel in the windowcolor c = color(r,g,b);pixels[loc] = c;} }寫入另一個 PImage 對象的像素
我們所有的圖像處理示例都從源圖像中讀取了每個像素,并將一個新像素直接寫入處理窗口。但是,將新像素寫入目標圖像(然后使用image()函數(shù)顯示)通常更方便。我們將在查看另一個簡單的像素操作時演示此技術:閾值。
閾值過濾器僅以兩種狀態(tài)(黑色或白色)中的一種顯示圖像的每個像素。該狀態(tài)是根據(jù)特定閾值設置的。如果像素的亮度大于閾值,我們將像素著色為白色,小于,黑色。在下面的代碼中,我們使用任意閾值 100。
示例:亮度閾值
PImage source; // Source image PImage destination; // Destination imagevoid setup() {size(200, 200);source = loadImage("sunflower.jpg");// The destination image is created as a blank image the same size as the source.destination = createImage(source.width, source.height, RGB); }void draw() {float threshold = 127;// We are going to look at both image's pixelssource.loadPixels();destination.loadPixels();for (int x = 0; x < source.width; x++) {for (int y = 0; y < source.height; y++ ) {int loc = x + y*source.width;// Test the brightness against the thresholdif (brightness(source.pixels[loc]) > threshold) {destination.pixels[loc] = color(255); // White} else {destination.pixels[loc] = color(0); // Black}}}// We changed the pixels in destinationdestination.updatePixels();// Display the destinationimage(destination,0,0); }作為 Processing 的filter()函數(shù)的一部分,此特定功能無需按像素處理即可使用。但是,如果您想實現(xiàn)自己的圖像處理算法,了解較低級別的代碼至關重要,而filter().
但是,如果您想要做的只是閾值,那么方法如下:
// Draw the image image(img,0,0); // Filter the window with a threshold effect // 0.5 means threshold is 50% brightness filter(THRESHOLD,0.5);二級:像素組處理
在前面的示例中,我們已經(jīng)看到源像素和目標像素之間存在一對一的關系。為了增加圖像的亮度,我們從源圖像中取出一個像素,增加 RGB 值,并在輸出窗口中顯示一個像素。為了執(zhí)行更高級的圖像處理功能,我們必須超越一對一的像素范式,進入像素組處理。
讓我們從源圖像的兩個像素中創(chuàng)建一個新像素開始——一個像素及其左側的鄰居。
如果我們知道像素位于 (x,y):
int loc = x + y*img.width; color pix = img.pixels[loc];然后它的左鄰居位于 (x-1,y):
int leftLoc = (x-1) + y*img.width; color leftPix = img.pixels[leftLoc];然后,我們可以根據(jù)像素與其左側相鄰像素之間的差異制作新顏色。
float diff = abs(brightness(pix) - brightness(leftPix)); pixels[loc] = color(diff);這是完整的算法:
示例:像素相鄰差異(邊緣)
// Since we are looking at left neighbors // We skip the first column for (int x = 1; x < width; x++) {for (int y = 0; y < height; y++ ) {// Pixel location and colorint loc = x + y*img.width;color pix = img.pixels[loc];// Pixel to the left location and colorint leftLoc = (x-1) + y*img.width;color leftPix = img.pixels[leftLoc];// New color is difference between pixel and left neighborfloat diff = abs(brightness(pix) - brightness(leftPix));pixels[loc] = color(diff);} }這個例子是一個簡單的水平邊緣檢測算法。當像素與其相鄰像素相差很大時,它們很可能是“邊緣”像素。例如,想一想黑色桌面上的一張白紙圖片。那張紙的邊緣是顏色最不同的地方,白色與黑色的交匯處。
在前面的示例中,我們查看了兩個像素以查找邊緣。然而,更復雜的算法通常涉及一次查看多個像素。畢竟,每個像素都有 8 個直接鄰居:左上、上、右上、右、右下、下、左下、左。
這些圖像處理算法通常被稱為“空間卷積”。該過程使用輸入像素及其相鄰像素的加權平均值來計算輸出像素。換言之,該新像素是像素面積的函數(shù)??梢允褂貌煌笮〉南噜弲^(qū)域,例如 3x3 矩陣、5x5 等。
每個像素的不同權重組合會產(chǎn)生不同的效果。例如,我們通過減去相鄰像素值并增加中心點像素來“銳化”圖像。通過取所有相鄰像素的平均值來實現(xiàn)模糊。(請注意,卷積矩陣中的值加起來為 1)。
例如,
Sharpen: -1 -1 -1 -1 9 -1 -1 -1 -1Blur: 1/9 1/9 1/9 1/9 1/9 1/9 1/9 1/9 1/9下面是一個使用 2D 數(shù)組執(zhí)行卷積的示例(參見第 13 章,第 XX 頁,回顧 2D 數(shù)組)來存儲 3x3 矩陣的像素權重。這個例子可能是迄今為止我們在本書中遇到的最高級的例子,因為它涉及很多元素(嵌套循環(huán)、2D 數(shù)組、PImage 像素等)。
示例:使用卷積銳化
PImage img; int w = 80;// It's possible to perform a convolution // the image with different matricesfloat[][] matrix = { { -1, -1, -1 },{ -1, 9, -1 },{ -1, -1, -1 } };void setup() {size(200, 200);frameRate(30);img = loadImage("sunflower.jpg"); }void draw() {// We're only going to process a portion of the image// so let's set the whole image as the background firstimage(img,0,0);// Where is the small rectangle we will processint xstart = constrain(mouseX-w/2,0,img.width);int ystart = constrain(mouseY-w/2,0,img.height);int xend = constrain(mouseX+w/2,0,img.width);int yend = constrain(mouseY+w/2,0,img.height);int matrixsize = 3;loadPixels();// Begin our loop for every pixelfor (int x = xstart; x < xend; x++) {for (int y = ystart; y < yend; y++ ) {// Each pixel location (x,y) gets passed into a function called convolution()// which returns a new color value to be displayed.color c = convolution(x,y,matrix,matrixsize,img);int loc = x + y*img.width;pixels[loc] = c;}}updatePixels();stroke(0);noFill();rect(xstart,ystart,w,w); }color convolution(int x, int y, float[][] matrix, int matrixsize, PImage img) {float rtotal = 0.0;float gtotal = 0.0;float btotal = 0.0;int offset = matrixsize / 2;// Loop through convolution matrixfor (int i = 0; i < matrixsize; i++){for (int j= 0; j < matrixsize; j++){// What pixel are we testingint xloc = x+i-offset;int yloc = y+j-offset;int loc = xloc + img.width*yloc;// Make sure we have not walked off the edge of the pixel arrayloc = constrain(loc,0,img.pixels.length-1);// Calculate the convolution// We sum all the neighboring pixels multiplied by the values in the convolution matrix.rtotal += (red(img.pixels[loc]) * matrix[i][j]);gtotal += (green(img.pixels[loc]) * matrix[i][j]);btotal += (blue(img.pixels[loc]) * matrix[i][j]);}}// Make sure RGB is within rangertotal = constrain(rtotal,0,255);gtotal = constrain(gtotal,0,255);btotal = constrain(btotal,0,255);// Return the resulting colorreturn color(rtotal,gtotal,btotal); }可視化圖像
你可能會想:“天哪,這一切都很有趣,但是說真的,當我想模糊圖像或改變它的亮度時,我真的需要編寫代碼嗎?我的意思是,我不能使用 Photoshop 嗎?” 實際上,我們在這里所取得的成果僅僅是對 Adob??e 高技能程序員所做工作的初步了解。然而,Processing 的強大之處在于實時、交互式圖形應用程序的潛力。我們沒有必要生活在“像素點”和“像素組”處理的范圍內。
以下是繪制處理形狀的算法的兩個示例。我們不再像過去那樣隨機或使用硬編碼值對形狀進行著色,而是從PImage對象內部的像素中選擇顏色。圖像本身從不顯示;相反,它是一個信息數(shù)據(jù)庫,我們可以利用它來進行多種創(chuàng)造性的追求。
在第一個示例中,對于通過 draw() 的每個循環(huán),我們在屏幕上的隨機位置填充一個橢圓,顏色取自源圖像中對應位置的顏色。結果是基本的“點畫式”效果:
示例:“點畫法”
PImage img; int pointillize = 16;void setup() {size(200,200);img = loadImage("sunflower.jpg");background(0);smooth(); }void draw() {// Pick a random pointint x = int(random(img.width));int y = int(random(img.height));int loc = x + y*img.width;// Look up the RGB color in the source imageloadPixels();float r = red(img.pixels[loc]);float g = green(img.pixels[loc]);float b = blue(img.pixels[loc]);noStroke();// Draw an ellipse at that location with that colorfill(r,g,b,100);ellipse(x,y,pointillize,pointillize); }在下一個示例中,我們從二維圖像中獲取數(shù)據(jù),并使用第 14 章中描述的 3D 平移技術,為三維空間中的每個像素渲染一個矩形。z 位置由顏色的亮度決定。較亮的顏色看起來更靠近觀察者,而較暗的顏色則更遠。
示例:2D 圖像映射到 3D
PImage img; // The source image int cellsize = 2; // Dimensions of each cell in the grid int cols, rows; // Number of columns and rows in our systemvoid setup() {size(200, 200, P3D);img = loadImage("sunflower.jpg"); // Load the imagecols = width/cellsize; // Calculate # of columnsrows = height/cellsize; // Calculate # of rows }void draw() {background(0);loadPixels();// Begin loop for columnsfor ( int i = 0; i < cols;i++) {// Begin loop for rowsfor ( int j = 0; j < rows;j++) {int x = i*cellsize + cellsize/2; // x positionint y = j*cellsize + cellsize/2; // y positionint loc = x + y*width; // Pixel array locationcolor c = img.pixels[loc]; // Grab the color// Calculate a z position as a function of mouseX and pixel brightnessfloat z = (mouseX/(float)width) * brightness(img.pixels[loc]) - 100.0;// Translate to the location, set fill and stroke, and draw the rectpushMatrix();translate(x,y,z);fill(c);noStroke();rectMode(CENTER);rect(0,0,cellsize,cellsize);popMatrix();}} }原文鏈接:https://processing.org/tutorials/pixels
總結
以上是生活随笔為你收集整理的图像和像素(Images and Pixels)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php 拼接html字符串,php截断带
- 下一篇: pandas(四)pandas的拼接操作