Compare commits
	
		
			17 Commits
		
	
	
		
			543eb21595
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 683fbd912f | |||
| 6df1a451ec | |||
| 791f6b7b88 | |||
| 979807f399 | |||
| 0657c28d7d | |||
| ee6999b095 | |||
| f8db769ec3 | |||
| cdd9a08635 | |||
| 0943bfa193 | |||
| 0a7a647126 | |||
| ddd8952e9a | |||
| 54fa312113 | |||
| ddddbfe558 | |||
| 9454948799 | |||
| 16e6e51472 | |||
| e33f77b46e | |||
| 309088e5d2 | 
							
								
								
									
										18
									
								
								Lab11.iml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,18 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <module type="WEB_MODULE" version="4"> | ||||||
|  |   <component name="NewModuleRootManager" inherit-compiler-output="true"> | ||||||
|  |     <exclude-output /> | ||||||
|  |     <content url="file://$MODULE_DIR$"> | ||||||
|  |       <sourceFolder url="file://$MODULE_DIR$/res" type="java-resource" /> | ||||||
|  |       <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> | ||||||
|  |     </content> | ||||||
|  |     <orderEntry type="inheritedJdk" /> | ||||||
|  |     <orderEntry type="sourceFolder" forTests="false" /> | ||||||
|  |     <orderEntry type="library" name="scala-sdk-2.13.12" level="project" /> | ||||||
|  |     <orderEntry type="library" name="bridj-0.7.0" level="project" /> | ||||||
|  |     <orderEntry type="library" name="slf4j-api-1.7.2" level="project" /> | ||||||
|  |     <orderEntry type="library" name="webcam-capture-0.3.12" level="project" /> | ||||||
|  |     <orderEntry type="library" name="bytedeco.javacv" level="project" /> | ||||||
|  |     <orderEntry type="library" name="bytedeco.javacv.platform" level="project" /> | ||||||
|  |   </component> | ||||||
|  | </module> | ||||||
							
								
								
									
										
											BIN
										
									
								
								libs/bridj-0.7.0.jar
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								libs/slf4j-api-1.7.2.jar
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								libs/webcam-capture-0.3.12.jar
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								res/Dead_tree.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 94 KiB | 
							
								
								
									
										
											BIN
										
									
								
								res/collins_eileen.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 285 KiB | 
							
								
								
									
										
											BIN
										
									
								
								res/grace_hopper.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 60 KiB | 
							
								
								
									
										
											BIN
										
									
								
								res/imageProcessing.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 140 KiB | 
							
								
								
									
										
											BIN
										
									
								
								res/imageProcessing_empty.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								res/mask.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 23 KiB | 
							
								
								
									
										
											BIN
										
									
								
								res/mask640x480.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 19 KiB | 
							
								
								
									
										
											BIN
										
									
								
								res/moire1.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 279 KiB | 
							
								
								
									
										
											BIN
										
									
								
								res/moire2.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 19 KiB | 
							
								
								
									
										
											BIN
										
									
								
								res/rice.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 14 KiB | 
							
								
								
									
										9
									
								
								src/TestArray.scala
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,9 @@ | |||||||
|  | object TestArray extends App { | ||||||
|  |   def arrayAvg(arr: Array[Double]): Double = { | ||||||
|  |     if (arr.isEmpty) return 0 | ||||||
|  |     return arr.sum / arr.length | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   val arr: Array[Double] = Array.range(1, 1001).map(i => i.toDouble) | ||||||
|  |   println(arrayAvg(arr)) | ||||||
|  | } | ||||||
							
								
								
									
										188
									
								
								src/imagefilters/ImageFilters.scala
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,188 @@ | |||||||
|  | package imagefilters | ||||||
|  |  | ||||||
|  | import java.awt.Color | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * This class implements the various image filters | ||||||
|  |  */ | ||||||
|  | object ImageFilters { | ||||||
|  |   // Default factors (reference values) | ||||||
|  |   /*val SEPIA_FACTORS: Array[Array[Double]] = Array( | ||||||
|  |     Array( 0.393, 0.769, 0.189), | ||||||
|  |     Array( 0.349, 0.686, 0.168), | ||||||
|  |     Array( 0.272, 0.534, 0.131) | ||||||
|  |   )*/ | ||||||
|  |  | ||||||
|  |   // Adapted factors (matches the example in the instructions) | ||||||
|  |   val SEPIA_FACTORS: Array[Array[Double]] = Array( | ||||||
|  |     Array( 0.883, 0.004, 0.003), | ||||||
|  |     Array(-0.058, 0.791,-0.097), | ||||||
|  |     Array( 0.034,-0.244, 0.583) | ||||||
|  |   ) | ||||||
|  |  | ||||||
|  |   def filter(src: Array[Array[Int]], func: (Int, Int, Int, Int, Int) => Int): Array[Array[Int]] = { | ||||||
|  |     val width: Int = src.length | ||||||
|  |     val height: Int = if (width == 0) 0 else src(0).length | ||||||
|  |     val dst: Array[Array[Int]] = Array.ofDim(width, height) | ||||||
|  |     for (x: Int <- 0 until width) { | ||||||
|  |       for (y: Int <- 0 until height) { | ||||||
|  |         dst(x)(y) = func(src(x)(y), x, y, width, height) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return dst | ||||||
|  |   } | ||||||
|  |   def filter(src: Array[Array[Int]], func: (Int) => Int): Array[Array[Int]] = filter(src, (value, x, y, width, height) => func(value)) | ||||||
|  |   def colorFilter(src: Array[Array[Color]], func: (Color, Int, Int, Int, Int) => Color): Array[Array[Color]] = { | ||||||
|  |     val width: Int = src.length | ||||||
|  |     val height: Int = if (width == 0) 0 else src(0).length | ||||||
|  |     val dst: Array[Array[Color]] = Array.ofDim(width, height) | ||||||
|  |     for (x: Int <- 0 until width) { | ||||||
|  |       for (y: Int <- 0 until height) { | ||||||
|  |         dst(x)(y) = func(src(x)(y), x, y, width, height) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return dst | ||||||
|  |   } | ||||||
|  |   def colorFilter(src: Array[Array[Color]], func: (Color) => Color): Array[Array[Color]] = colorFilter(src, (value, x, y, width, height) => func(value)) | ||||||
|  |  | ||||||
|  |   def duplicate(a: Array[Array[Int]]): Array[Array[Int]] = filter(a, (value) => value) | ||||||
|  |   def threshold(a: Array[Array[Int]], thresh: Int): Array[Array[Int]] = filter(a, (value) => if (value > thresh) 255 else 0) | ||||||
|  |   def mean(a: Array[Array[Int]]): Array[Array[Int]] = filter(a, (value, x, y, width, height) => { | ||||||
|  |     if (x == 0 || x == width - 1 || y == 0 || y == height - 1) value | ||||||
|  |     else { | ||||||
|  |       var avg: Double = 0 | ||||||
|  |       for (dx: Int <- -1 to 1) { | ||||||
|  |         for (dy: Int <- -1 to 1) { | ||||||
|  |           avg += a(x+dx)(y+dy) | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       (avg/9.0).toInt | ||||||
|  |     } | ||||||
|  |   }) | ||||||
|  |   def mean(a: Array[Array[Int]], radius: Int): Array[Array[Int]] = { | ||||||
|  |     if (radius < 0) throw new IllegalArgumentException("radius must be >= 0") | ||||||
|  |     val diameter: Int = 2 * radius + 1 | ||||||
|  |  | ||||||
|  |     filter(a, (value, x, y, width, height) => { | ||||||
|  |       if (x <= radius - 1 || x >= width - radius || y <= radius - 1 || y >= height - radius) value | ||||||
|  |       else { | ||||||
|  |         var avg: Double = 0 | ||||||
|  |         for (dx: Int <- -radius to radius) { | ||||||
|  |           for (dy: Int <- -radius to radius) { | ||||||
|  |             avg += a(x+dx)(y+dy) | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         (avg/(diameter * diameter)).toInt | ||||||
|  |       } | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   def edges(a: Array[Array[Int]]): Array[Array[Int]] = filter(a, (value, x, y, width, height) => { | ||||||
|  |     if (x == 0 || x == width - 1 || y == 0 || y == height - 1) value | ||||||
|  |     else { | ||||||
|  |       val dx: Int = a(x+1)(y) - a(x-1)(y) | ||||||
|  |       val dy: Int = a(x)(y+1) - a(x)(y-1) | ||||||
|  |       Math.sqrt(dx*dx + dy*dy).toInt | ||||||
|  |     } | ||||||
|  |   }) | ||||||
|  |  | ||||||
|  |   def sobel(a: Array[Array[Int]], intensityFactor: Double): Array[Array[Int]] = filter(a, (value, x, y, width, height) => { | ||||||
|  |     if (x == 0 || x == width - 1 || y == 0 || y == height - 1) value | ||||||
|  |     else { | ||||||
|  |       val dx: Int = a(x+1)(y-1) + 2*a(x+1)(y) + a(x+1)(y+1) - a(x-1)(y-1) - 2*a(x-1)(y) - a(x-1)(y+1) | ||||||
|  |       val dy: Int = a(x-1)(y+1) + 2*a(x)(y+1) + a(x+1)(y+1) - a(x-1)(y-1) - 2*a(x)(y-1) - a(x+1)(y-1) | ||||||
|  |       (Math.sqrt(dx*dx + dy*dy)*intensityFactor).toInt | ||||||
|  |     } | ||||||
|  |   }) | ||||||
|  |  | ||||||
|  |   def noise(a: Array[Array[Int]], intensity: Double): Array[Array[Int]] = filter(a, (value) => { | ||||||
|  |     Math.max(0, Math.min(255, value + (Math.random()*2-1)*intensity)).toInt | ||||||
|  |   }) | ||||||
|  |  | ||||||
|  |   def noise(a: Array[Array[Color]], intensity: Double): Array[Array[Color]] = colorFilter(a, (col) => { | ||||||
|  |     val r: Double = col.getRed + (Math.random()*2-1)*intensity | ||||||
|  |     val g: Double = col.getGreen + (Math.random()*2-1)*intensity | ||||||
|  |     val b: Double = col.getBlue + (Math.random()*2-1)*intensity | ||||||
|  |     new Color( | ||||||
|  |       Math.max(0, Math.min(255, r)).toInt, | ||||||
|  |       Math.max(0, Math.min(255, g)).toInt, | ||||||
|  |       Math.max(0, Math.min(255, b)).toInt | ||||||
|  |     ) | ||||||
|  |   }) | ||||||
|  |  | ||||||
|  |   def mask(a: Array[Array[Color]], maskImg: Array[Array[Int]]): Array[Array[Color]] = { | ||||||
|  |     val maskWidth: Int = maskImg.length | ||||||
|  |     val maskHeight: Int = if (maskWidth == 0) 0 else maskImg(0).length | ||||||
|  |  | ||||||
|  |     colorFilter(a, (col, x, y, width, height) => { | ||||||
|  |       val maskX: Int = (x.toDouble / width * maskWidth).toInt | ||||||
|  |       val maskY: Int = (y.toDouble / height * maskHeight).toInt | ||||||
|  |       val factor: Double = maskImg(maskX)(maskY)/255.0 | ||||||
|  |       new Color( | ||||||
|  |         (col.getRed * factor).toInt, | ||||||
|  |         (col.getGreen * factor).toInt, | ||||||
|  |         (col.getBlue * factor).toInt | ||||||
|  |       ) | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   def getAvgInPixel(a: Array[Array[Color]], pixelSize: Int, px: Int, py: Int): Color = { | ||||||
|  |     val width: Int = a.length | ||||||
|  |     val height: Int = if (width == 0) 0 else a(0).length | ||||||
|  |     var r: Double = 0 | ||||||
|  |     var g: Double = 0 | ||||||
|  |     var b: Double = 0 | ||||||
|  |     var cnt: Int = 0 | ||||||
|  |     for (x: Int <- px*pixelSize until (px+1)*pixelSize) { | ||||||
|  |       for (y: Int <- py*pixelSize until (py+1)*pixelSize) { | ||||||
|  |         if (x < width && y < height) { | ||||||
|  |           cnt += 1 | ||||||
|  |           r += a(x)(y).getRed | ||||||
|  |           g += a(x)(y).getGreen | ||||||
|  |           b += a(x)(y).getBlue | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return new Color((r/cnt).toInt, (g/cnt).toInt, (b/cnt).toInt) | ||||||
|  |   } | ||||||
|  |   def pixelize(a: Array[Array[Color]], pixelSize: Int): Array[Array[Color]] = { | ||||||
|  |     val width: Int = a.length | ||||||
|  |     val height: Int = if (width == 0) 0 else a(0).length | ||||||
|  |     val hPixels: Int = Math.ceil(width.toDouble/pixelSize).toInt | ||||||
|  |     val vPixels: Int = Math.ceil(height.toDouble/pixelSize).toInt | ||||||
|  |     val pixels: Array[Array[Color]] = Array.ofDim[Color](hPixels, vPixels) | ||||||
|  |     for (x: Int <- 0 until hPixels) { | ||||||
|  |       for (y: Int <- 0 until vPixels) { | ||||||
|  |         pixels(x)(y) = getAvgInPixel(a, pixelSize, x, y) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     colorFilter(a, (col, x, y, w, h) => pixels(x/pixelSize)(y/pixelSize)) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   def pixelize(a: Array[Array[Color]], pixelSize: Int, x1: Int, y1: Int, x2: Int, y2: Int): Array[Array[Color]] = { | ||||||
|  |     val pixelated: Array[Array[Color]] = pixelize(a, pixelSize) | ||||||
|  |     colorFilter(a, (col, x, y, w, h) => { | ||||||
|  |       if (x1 <= x && x <= x2 && y1 <= y && y <= y2) { | ||||||
|  |         pixelated(x)(y) | ||||||
|  |       } | ||||||
|  |       else col | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   def sepia(a: Array[Array[Color]]): Array[Array[Color]] = colorFilter(a, (col) => { | ||||||
|  |     val r: Double = Math.max(0, Math.min(255, col.getRed   * SEPIA_FACTORS(0)(0) + | ||||||
|  |                                               col.getGreen * SEPIA_FACTORS(0)(1) + | ||||||
|  |                                               col.getBlue  * SEPIA_FACTORS(0)(2))) | ||||||
|  |  | ||||||
|  |     val g: Double = Math.max(0, Math.min(255, col.getRed   * SEPIA_FACTORS(1)(0) + | ||||||
|  |                                               col.getGreen * SEPIA_FACTORS(1)(1) + | ||||||
|  |                                               col.getBlue  * SEPIA_FACTORS(1)(2))) | ||||||
|  |  | ||||||
|  |     val b: Double = Math.max(0, Math.min(255, col.getRed   * SEPIA_FACTORS(2)(0) + | ||||||
|  |                                               col.getGreen * SEPIA_FACTORS(2)(1) + | ||||||
|  |                                               col.getBlue  * SEPIA_FACTORS(2)(2))) | ||||||
|  |  | ||||||
|  |     new Color(r.toInt, g.toInt, b.toInt) | ||||||
|  |   }) | ||||||
|  | } | ||||||
							
								
								
									
										64
									
								
								src/imagefilters/ImageProcessingApp.scala
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,64 @@ | |||||||
|  | package imagefilters | ||||||
|  |  | ||||||
|  | import isc.graphics.ImageGraphics | ||||||
|  |  | ||||||
|  | object ImageProcessingApp extends App { | ||||||
|  |  | ||||||
|  | 	/*val imageFile = "./res/collins_eileen.png" | ||||||
|  | 	val org = new ImageGraphics(imageFile, "Original", -500, -250) | ||||||
|  | 	val dest = new ImageGraphics(imageFile, "Duplicate", 0, -250) | ||||||
|  | 	val thresh = new ImageGraphics(imageFile, "Threshold", -500, 250) | ||||||
|  |  | ||||||
|  |     // Simple copy and display | ||||||
|  | 	val newPixels = ImageFilters.duplicate(org.getPixelsBW()) | ||||||
|  | 	dest.setPixelsBW(newPixels) | ||||||
|  |  | ||||||
|  | 	thresh.setPixelsBW(ImageFilters.threshold(newPixels, 128)) | ||||||
|  |  | ||||||
|  | 	val mean = new ImageGraphics("./res/imageProcessing.jpg", "Mean", 0, 250) | ||||||
|  | 	mean.setPixelsBW(ImageFilters.mean(mean.getPixelsBW())) | ||||||
|  |  | ||||||
|  | 	val mean2 = new ImageGraphics("./res/imageProcessing.jpg", "Mean 2", 250, 250) | ||||||
|  | 	mean2.setPixelsBW(ImageFilters.mean(mean2.getPixelsBW(), 4)) | ||||||
|  |  | ||||||
|  | 	val edges = new ImageGraphics("./res/rice.jpg", "Edges", 500, 250) | ||||||
|  | 	edges.setPixelsBW(ImageFilters.edges(edges.getPixelsBW()))*/ | ||||||
|  |  | ||||||
|  | 	/*val imageFile: String = "./res/grace_hopper.jpg" | ||||||
|  | 	val org = new ImageGraphics(imageFile, "Original", -768, -512) | ||||||
|  | 	val bw = new ImageGraphics(imageFile, "Black & White", -256, -512) | ||||||
|  | 	val threshold = new ImageGraphics(imageFile, "Threshold", 256, -512) | ||||||
|  | 	val blur = new ImageGraphics(imageFile, "Blurred", -768, 0) | ||||||
|  | 	val edges = new ImageGraphics(imageFile, "Edge detection", -256, 0) | ||||||
|  | 	val sobel = new ImageGraphics(imageFile, "Sobel", 256, 0) | ||||||
|  | 	val noise = new ImageGraphics(imageFile, "Noise", -768, 512) | ||||||
|  |  | ||||||
|  | 	bw.setPixelsBW(ImageFilters.duplicate(org.getPixelsBW())) | ||||||
|  | 	threshold.setPixelsBW(ImageFilters.threshold(org.getPixelsBW(), 128)) | ||||||
|  | 	blur.setPixelsBW(ImageFilters.mean(org.getPixelsBW(), 3)) | ||||||
|  | 	edges.setPixelsBW(ImageFilters.edges(org.getPixelsBW())) | ||||||
|  | 	sobel.setPixelsBW(ImageFilters.sobel(org.getPixelsBW(), 0.3)) | ||||||
|  | 	noise.setPixelsBW(ImageFilters.noise(org.getPixelsBW(), 30))*/ | ||||||
|  |  | ||||||
|  | 	/*val imageFile: String = "./res/collins_eileen.png" | ||||||
|  | 	val maskFile: String = "./res/mask.png" | ||||||
|  | 	val org = new ImageGraphics(imageFile, "Original", -768, -512) | ||||||
|  | 	val mask = new ImageGraphics(maskFile, "Mask", -256, -512) | ||||||
|  | 	val masked = new ImageGraphics(imageFile, "Masked", 256, -512) | ||||||
|  | 	masked.setPixelsColor(ImageFilters.mask(org.getPixelsColor(), mask.getPixelsBW()))*/ | ||||||
|  |  | ||||||
|  | 	/*val imageFile: String = "./res/grace_hopper.jpg" | ||||||
|  | 	val org = new ImageGraphics(imageFile, "Original", -768, -512) | ||||||
|  | 	val pixelated = new ImageGraphics(imageFile, "Pixelated", -256, -512) | ||||||
|  | 	pixelated.setPixelsColor(ImageFilters.pixelize(org.getPixelsColor(), 30, 130, 120, 400, 230))*/ | ||||||
|  |  | ||||||
|  | 	val imageFile: String = "./res/collins_eileen.png" | ||||||
|  | 	val org = new ImageGraphics(imageFile, "Original", -768, -512) | ||||||
|  | 	val sepia = new ImageGraphics(imageFile, "Sepia", -256, -512) | ||||||
|  | 	val noise = new ImageGraphics(imageFile, "Noise", 256, -512) | ||||||
|  | 	val mask = new ImageGraphics("./res/mask.png", "Mask", -256, -256) | ||||||
|  | 	val masked = new ImageGraphics(imageFile, "Masked", 256, -256) | ||||||
|  | 	sepia.setPixelsColor(ImageFilters.sepia(org.getPixelsColor())) | ||||||
|  | 	noise.setPixelsColor(ImageFilters.noise(org.getPixelsColor(), 50)) | ||||||
|  | 	masked.setPixelsColor(ImageFilters.mask(org.getPixelsColor(), mask.getPixelsBW())) | ||||||
|  | } | ||||||
							
								
								
									
										241
									
								
								src/isc/graphics/ImageGraphics.scala
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,241 @@ | |||||||
|  | package isc.graphics | ||||||
|  |  | ||||||
|  | import java.awt._ | ||||||
|  | import java.awt.image.BufferedImage | ||||||
|  | import java.io.{File, FileInputStream} | ||||||
|  | import javax.imageio.ImageIO | ||||||
|  | import javax.swing.{JFrame, JPanel} | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * [ImageGraphics] helpers functions in companion object. | ||||||
|  |  */ | ||||||
|  | object ImageGraphics { | ||||||
|  | 	/** | ||||||
|  | 	 * Converts a color array to a black-or-white array | ||||||
|  | 	 * | ||||||
|  | 	 * @param c The color array | ||||||
|  | 	 * @return The array converted to BW | ||||||
|  | 	 */ | ||||||
|  | 	def convertToGray(c: Array[Array[Color]]): Array[Array[Color]] = { | ||||||
|  | 		val w = c.length | ||||||
|  | 		val h = c(0).length | ||||||
|  | 		val values = Array.ofDim[Color](w, h) | ||||||
|  |  | ||||||
|  | 		// FIXME this is slow | ||||||
|  | 		for (i <- 0 until w) { | ||||||
|  | 			for (j <- 0 until h) { | ||||||
|  | 				val col = c(i)(j) | ||||||
|  | 				val intColor = (0.3 * col.getRed + 0.59 * col.getGreen + 0.11 * col.getBlue).toInt | ||||||
|  | 				values(i)(j) = new Color(intColor, intColor, intColor) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		values | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	def convertToGrayInt(c: Array[Array[Color]]): Array[Array[Int]] = { | ||||||
|  | 		val w = c.length | ||||||
|  | 		val h = c(0).length | ||||||
|  | 		val values = Array.ofDim[Int](w, h) | ||||||
|  | 		for (i <- 0 until w) { | ||||||
|  | 			for (j <- 0 until h) { | ||||||
|  | 				val col = c(i)(j) | ||||||
|  | 				val intColor = (0.3 * col.getRed + 0.59 * col.getGreen + 0.11 * col.getBlue).toInt | ||||||
|  | 				values(i)(j) = intColor | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		values | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	def main(args: Array[String]): Unit = { | ||||||
|  | 		val imageUsed = "res/grace_hopper.jpg" | ||||||
|  | 		val org = new ImageGraphics(imageUsed, "Original", 0, 0) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * This class was made to deal with images as multidimensional arrays. | ||||||
|  |  * Mainly used in the <code>ImageProcessing</code> lab. It expects the images to reside in the <code>src</code> directory | ||||||
|  |  * | ||||||
|  |  * @author Pierre-André Mudry | ||||||
|  |  * @version 1.0 | ||||||
|  |  * @constructor | ||||||
|  |  * | ||||||
|  |  * @param filePath        the path of the file, relative to the project. For instance, "./res/grace_hopper.jpg" | ||||||
|  |  * @param windowTitle     the title | ||||||
|  |  * @param xPositionOffset the x offset | ||||||
|  |  * @param yPositionOffset the y offet | ||||||
|  |  */ | ||||||
|  | class ImageGraphics(val filePath: String, val windowTitle: String, | ||||||
|  | 						  val xPositionOffset: Int, val yPositionOffset: Int) extends JFrame { | ||||||
|  |  | ||||||
|  | 	private var imgBuffer: BufferedImage = null | ||||||
|  | 	private var w = 0 | ||||||
|  | 	private var h = 0 | ||||||
|  |  | ||||||
|  | 	class ImgPanel extends JPanel { | ||||||
|  | 		override def paint(g: Graphics): Unit = { | ||||||
|  | 			super.paint(g) | ||||||
|  | 			g.drawImage(imgBuffer, 0, 0, null) | ||||||
|  | 			g.dispose() | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	val imgPanel = new ImgPanel() | ||||||
|  |  | ||||||
|  | 	try { // Fill the frame content with the image | ||||||
|  | 		try { | ||||||
|  | 			imgBuffer = ImageIO.read(new FileInputStream(new File(filePath))) | ||||||
|  | 			w = imgBuffer.getWidth | ||||||
|  | 			h = imgBuffer.getHeight | ||||||
|  | 		} catch { | ||||||
|  | 			case e: Exception => | ||||||
|  | 				println("Could not load directly, using classpath.") | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// If the first technique failed, try using resource instead | ||||||
|  | 		if (imgBuffer == null) { | ||||||
|  | 			try { | ||||||
|  | 				imgBuffer = ImageIO.read(classOf[ImageGraphics].getResource(filePath)) | ||||||
|  | 				w = imgBuffer.getWidth | ||||||
|  | 				h = imgBuffer.getHeight | ||||||
|  | 			} catch { | ||||||
|  | 				case e: Exception => | ||||||
|  | 					System.err.println("Could not find image " + filePath + ", exiting !") | ||||||
|  | 					e.printStackTrace() | ||||||
|  | 					System.exit(-1) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		this.setResizable(false) | ||||||
|  | 		this.setTitle(windowTitle) | ||||||
|  | 		this.setLayout(new BorderLayout()) | ||||||
|  | 		this.setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE) | ||||||
|  |  | ||||||
|  | 		imgPanel.setPreferredSize(new Dimension(imgBuffer.getWidth, imgBuffer.getHeight)) | ||||||
|  | 		this.add(imgPanel, BorderLayout.CENTER) | ||||||
|  | 		this.pack() | ||||||
|  |  | ||||||
|  | 		// Get the size of the screen | ||||||
|  | 		val dim = Toolkit.getDefaultToolkit.getScreenSize | ||||||
|  |  | ||||||
|  | 		// Determine the new location of the window | ||||||
|  | 		val lw = this.getSize.width | ||||||
|  | 		val lh = this.getSize.height | ||||||
|  | 		val x = (dim.width - lw) / 2 + xPositionOffset | ||||||
|  | 		val y = (dim.height - lh) / 2 + yPositionOffset | ||||||
|  |  | ||||||
|  | 		// Move the window | ||||||
|  | 		this.setLocation(x, y) | ||||||
|  | 		this.setVisible(true) | ||||||
|  | 	} catch { | ||||||
|  | 		case e: Exception => | ||||||
|  | 			e.printStackTrace() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Sets a grayscale pixel, does not sets values for invalid pixels | ||||||
|  | 	 * outside the screen. Does not repaint the screen either because it | ||||||
|  | 	 * is slow. | ||||||
|  | 	 * | ||||||
|  | 	 * @param x | ||||||
|  | 	 * @param y | ||||||
|  | 	 * @param intensity | ||||||
|  | 	 */ | ||||||
|  | 	def setPixelBW(x: Int, y: Int, intensity: Int): Unit = { | ||||||
|  | 		if (!((x < 0) || (y < 0) || (x >= w) || (y >= h))) | ||||||
|  | 			imgBuffer.setRGB(x, y, intensity << 16 | intensity << 8 | intensity) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Sets an array of grayscale pixels (from 0 to 255) and displays them | ||||||
|  | 	 * | ||||||
|  | 	 * @param pixels | ||||||
|  | 	 */ | ||||||
|  | 	def setPixelsBW(pixels: Array[Array[Int]]): Unit = { | ||||||
|  | 		try { | ||||||
|  | 			if (pixels(0).length != h || pixels.length != w) throw new Exception("Invalid size of the pixel array !") | ||||||
|  | 			for (i <- 0 until w) { | ||||||
|  | 				for (j <- 0 until h) { | ||||||
|  | 					val c = pixels(i)(j) << 16 | pixels(i)(j) << 8 | pixels(i)(j) | ||||||
|  | 					imgBuffer.setRGB(i, j, c) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			this.repaint() | ||||||
|  | 		} catch { | ||||||
|  | 			case e: Exception => | ||||||
|  | 				e.printStackTrace() | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Sets an array of pixels of Color and displays them | ||||||
|  | 	 * | ||||||
|  | 	 * @param pixels | ||||||
|  | 	 */ | ||||||
|  | 	def setPixelsColor(pixels: Array[Array[Color]]): Unit = { | ||||||
|  | 		try { | ||||||
|  | 			if (pixels(0).length != h || pixels.length != w) throw new Exception("Invalid size of the pixel array !") | ||||||
|  |  | ||||||
|  | 			for (i <- 0 until w) { | ||||||
|  | 				for (j <- 0 until h) { | ||||||
|  | 					imgBuffer.setRGB(i, j, pixels(i)(j).getRGB) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			this.repaint() | ||||||
|  | 		} catch { | ||||||
|  | 			case e: Exception => | ||||||
|  | 				e.printStackTrace() | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Gets a single pixel from the background image and returns its | ||||||
|  | 	 * grayscale value | ||||||
|  | 	 * | ||||||
|  | 	 * @param x the x coordinate | ||||||
|  | 	 * @param y the y coordinate | ||||||
|  | 	 * @return the pixel | ||||||
|  | 	 */ | ||||||
|  | 	def getPixelBW(x: Int, y: Int): Int = | ||||||
|  | 		if ((x < 0) || (y < 0) || (x >= w) || (y >= h)) { | ||||||
|  | 			0 | ||||||
|  | 		} | ||||||
|  | 		else { | ||||||
|  | 			// Inside the image. Make the gray conversion and return the value | ||||||
|  | 			val c = new Color(imgBuffer.getRGB(x, y)) | ||||||
|  | 			(0.3 * c.getRed + 0.59 * c.getGreen + 0.11 * c.getBlue).toInt | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Gets the array of the pixels (which have been converted to grayscale | ||||||
|  | 	 * if required) | ||||||
|  | 	 * | ||||||
|  | 	 * @return The arrays of gray pixels | ||||||
|  | 	 */ | ||||||
|  | 	def getPixelsBW(): Array[Array[Int]] = { | ||||||
|  | 		val values = Array.ofDim[Int](w, h) | ||||||
|  |  | ||||||
|  | 		for (i <- 0 until w) { | ||||||
|  | 			for (j <- 0 until h) { | ||||||
|  | 				val c = new Color(imgBuffer.getRGB(i, j)) | ||||||
|  | 				values(i)(j) = (0.3 * c.getRed + 0.59 * c.getGreen + 0.11 * c.getBlue).toInt | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		values | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Gets the array of the pixels as Colors (see #Color) | ||||||
|  | 	 * | ||||||
|  | 	 * @return The arrays of pixels | ||||||
|  | 	 */ | ||||||
|  | 	def getPixelsColor(): Array[Array[Color]] = { | ||||||
|  | 		val values = Array.ofDim[Color](w, h) | ||||||
|  | 		for (i <- 0 until w) { | ||||||
|  | 			for (j <- 0 until h) { | ||||||
|  | 				values(i)(j) = new Color(imgBuffer.getRGB(i, j)) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		values | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										36
									
								
								src/livefilter/FaceDetection.scala
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,36 @@ | |||||||
|  | package livefilter | ||||||
|  |  | ||||||
|  | import org.bytedeco.javacpp.Loader | ||||||
|  | import org.bytedeco.javacpp.indexer.UByteRawIndexer | ||||||
|  | import org.bytedeco.opencv.global.opencv_core.CV_8U | ||||||
|  | import org.bytedeco.opencv.opencv_core.{Mat, Rect, RectVector} | ||||||
|  | import org.bytedeco.opencv.opencv_objdetect.CascadeClassifier | ||||||
|  |  | ||||||
|  | import java.io.File | ||||||
|  | import java.net.URL | ||||||
|  |  | ||||||
|  | object FaceDetection { | ||||||
|  |   val url: URL = new URL("https://raw.github.com/opencv/opencv/master/data/haarcascades/haarcascade_frontalface_alt.xml") | ||||||
|  |   val file: File = Loader.cacheResource(url) | ||||||
|  |   val classifierName: String = file.getAbsolutePath | ||||||
|  |   val cascade: CascadeClassifier = new CascadeClassifier(classifierName) | ||||||
|  |  | ||||||
|  |   def detectFaces(src: Array[Array[Int]]): Array[Rect] = { | ||||||
|  |     val grayFrame: Mat = new Mat() | ||||||
|  |     val width: Int = src.length | ||||||
|  |     val height: Int = if (width == 0) 0 else src(0).length | ||||||
|  |     grayFrame.create(height, width, CV_8U) | ||||||
|  |  | ||||||
|  |     val idx: UByteRawIndexer = grayFrame.createIndexer() | ||||||
|  |  | ||||||
|  |     for (x: Int <- 0 until width) { | ||||||
|  |       for (y: Int <- 0 until height) { | ||||||
|  |         idx.put(y, x, src(x)(y)) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     val faces: RectVector = new RectVector() | ||||||
|  |     cascade.detectMultiScale(grayFrame, faces) | ||||||
|  |     return faces.get() | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										103
									
								
								src/livefilter/LiveFilter.scala
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,103 @@ | |||||||
|  | package livefilter | ||||||
|  |  | ||||||
|  | import com.github.sarxos.webcam.WebcamPanel.DrawMode | ||||||
|  | import com.github.sarxos.webcam.{Webcam, WebcamImageTransformer, WebcamPanel, WebcamResolution} | ||||||
|  | import imagefilters.ImageFilters | ||||||
|  | import org.bytedeco.opencv.opencv_core.{Rect, RectVector} | ||||||
|  |  | ||||||
|  | import java.awt.image.BufferedImage | ||||||
|  | import java.awt.{Color, Dimension} | ||||||
|  | import java.io.{File, FileInputStream} | ||||||
|  | import javax.imageio.ImageIO | ||||||
|  | import javax.swing.JFrame | ||||||
|  |  | ||||||
|  | object LiveFilter { | ||||||
|  | 	def main(args: Array[String]): Unit = { | ||||||
|  | 		new LiveFilter() | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * A live filter class using VGA resolution | ||||||
|  |  */ | ||||||
|  | class LiveFilter extends WebcamImageTransformer { | ||||||
|  | 	val size: Dimension = WebcamResolution.VGA.getSize | ||||||
|  |  | ||||||
|  | 	val MASK: String = "./res/mask640x480.png" | ||||||
|  | 	val videoMask: Array[Array[Int]] = toBW(ImageIO.read(new FileInputStream(new File(MASK)))) | ||||||
|  |  | ||||||
|  | 	// Gets default webcam and sets image transformer to this (transformer will modify | ||||||
|  | 	// image after it's been received from webcam, in this case it will rotate it) | ||||||
|  | 	val webcam: Webcam = Webcam.getDefault | ||||||
|  |  | ||||||
|  | 	//val webcam: Webcam = Webcam.getWebcamByName("/dev/video2") | ||||||
|  |  | ||||||
|  | 	webcam.setViewSize(size) | ||||||
|  | 	println(size) | ||||||
|  | 	webcam.setImageTransformer(this) | ||||||
|  | 	webcam.open | ||||||
|  |  | ||||||
|  | 	// Create a window | ||||||
|  | 	val window = new JFrame("Test filter") | ||||||
|  |  | ||||||
|  | 	// Creates a panel to put the webcam image in it | ||||||
|  | 	val panel = new WebcamPanel(webcam) | ||||||
|  | 	panel.setFPSDisplayed(true) | ||||||
|  | 	panel.setDrawMode(DrawMode.FIT) | ||||||
|  |  | ||||||
|  | 	// Add panel to window | ||||||
|  | 	window.add(panel) | ||||||
|  | 	window.pack() | ||||||
|  | 	window.setVisible(true) | ||||||
|  |  | ||||||
|  | 	window.setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE) | ||||||
|  |  | ||||||
|  | 	def toBW(img: BufferedImage): Array[Array[Int]] = { | ||||||
|  | 		val o = Array.ofDim[Int](img.getWidth(), img.getHeight()) | ||||||
|  | 		for (x <- o.indices) { | ||||||
|  | 			for (y <- o(0).indices) { | ||||||
|  | 				val c = new Color(img.getRGB(x, y)) | ||||||
|  | 				o(x)(y) = (0.3 * c.getRed + 0.59 * c.getGreen + 0.11 * c.getBlue).toInt | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		o | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	def toArray(image: BufferedImage): Array[Array[Color]] = { | ||||||
|  | 		val out = Array.ofDim[Color](image.getWidth, image.getHeight) | ||||||
|  |  | ||||||
|  | 		for (x <- out.indices; y <- out(0).indices) { | ||||||
|  | 			out(x)(y) = new Color(image.getRGB(x, y)) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		out | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	private def updateImage(in: Array[Array[Color]], out: BufferedImage): Unit = { | ||||||
|  | 		for (x <- in.indices; y <- in(0).indices) { | ||||||
|  | 			out.setRGB(x, y, in(x)(y).getRGB) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Applies the various filters to the buffer | ||||||
|  | 	 * | ||||||
|  | 	 * @param image The input image | ||||||
|  | 	 * @return The filtered image | ||||||
|  | 	 */ | ||||||
|  | 	override def transform(image: BufferedImage): BufferedImage = { | ||||||
|  | 		val img: Array[Array[Color]] = toArray(image) | ||||||
|  |  | ||||||
|  | 		val bw: Array[Array[Int]] = toBW(image) | ||||||
|  | 		val faces: Array[Rect] = FaceDetection.detectFaces(bw) | ||||||
|  | 		var filtered: Array[Array[Color]] = img | ||||||
|  | 		for (rect: Rect <- faces) { | ||||||
|  |       filtered = ImageFilters.pixelize(filtered, 10, rect.x, rect.y, rect.x+rect.width, rect.y+rect.height) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | 		//val filtered: Array[Array[Color]] = ImageFilters.mask(ImageFilters.sepia(ImageFilters.noise(img, 10)), videoMask) | ||||||
|  |  | ||||||
|  | 		updateImage(filtered, image) | ||||||
|  | 		return image | ||||||
|  | 	} | ||||||
|  | } | ||||||