2016 Update

This commit is contained in:
Tina Müller 2016-12-05 22:15:40 +01:00
parent 948b86eafa
commit dcf5d15da3
7965 changed files with 139854 additions and 31002 deletions

View file

@ -1,4 +1,7 @@
Implement the [[wp:Hough transform|Hough transform]], which is used as part of feature extraction with digital images. It is a tool that makes it far easier to identify straight lines in the source image, whatever their orientation.
;Task:
Implement the [[wp:Hough transform|Hough transform]], which is used as part of feature extraction with digital images.
It is a tool that makes it far easier to identify straight lines in the source image, whatever their orientation.
The transform maps each point in the target image, <math>(\rho,\theta)</math>, to the average color of the pixels on the corresponding line of the source image (in <math>(x,y)</math>-space, where the line corresponds to points of the form <math>x\cos\theta + y\sin\theta = \rho</math>). The idea is that where there is a straight line in the original image, it corresponds to a bright (or dark, depending on the color of the background field) spot; by applying a suitable filter to the results of the transform, it is possible to extract the locations of the lines in the original image.
@ -6,3 +9,4 @@ The transform maps each point in the target image, <math>(\rho,\theta)</math>, t
The target space actually uses polar coordinates, but is conventionally plotted on rectangular coordinates for display. There's no specification of exactly how to map polar coordinates to a flat surface for display, but a convenient method is to use one axis for <math>\theta</math> and the other for <math>\rho</math>, with the center of the source image being the origin.
There is also a spherical Hough transform, which is more suited to identifying planes in 3D data.
<br><br>

View file

@ -0,0 +1,93 @@
import java.awt.image.*
import java.io.File
import javax.imageio.*
internal class ArrayData(val dataArray: IntArray, val width: Int, val height: Int) {
constructor(width: Int, height: Int) : this(IntArray(width * height), width, height) {
}
operator fun get(x: Int, y: Int) = dataArray[y * width + x]
operator fun set(x: Int, y: Int, value: Int) {
dataArray[y * width + x] = value
}
operator fun invoke(thetaAxisSize: Int, rAxisSize: Int, minContrast: Int): ArrayData {
val maxRadius = Math.ceil(Math.hypot(width.toDouble(), height.toDouble())).toInt()
val halfRAxisSize = rAxisSize.ushr(1)
val outputData = ArrayData(thetaAxisSize, rAxisSize)
// x output ranges from 0 to pi
// y output ranges from -maxRadius to maxRadius
val sinTable = DoubleArray(thetaAxisSize)
val cosTable = DoubleArray(thetaAxisSize)
for (theta in thetaAxisSize - 1 downTo 0) {
val thetaRadians = theta * Math.PI / thetaAxisSize
sinTable[theta] = Math.sin(thetaRadians)
cosTable[theta] = Math.cos(thetaRadians)
}
for (y in height - 1 downTo 0)
for (x in width - 1 downTo 0)
if (contrast(x, y, minContrast))
for (theta in thetaAxisSize - 1 downTo 0) {
val r = cosTable[theta] * x + sinTable[theta] * y
val rScaled = Math.round(r * halfRAxisSize / maxRadius).toInt() + halfRAxisSize
outputData.accumulate(theta, rScaled, 1)
}
return outputData
}
fun writeOutputImage(filename: String) {
val max = dataArray.max()!!
val image = BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB)
for (y in 0..height - 1)
for (x in 0..width - 1) {
val n = Math.min(Math.round(this[x, y] * 255.0 / max).toInt(), 255)
image.setRGB(x, height - 1 - y, n shl 16 or (n shl 8) or 0x90 or -0x01000000)
}
ImageIO.write(image, "PNG", File(filename))
}
private fun accumulate(x: Int, y: Int, delta: Int) {
set(x, y, get(x, y) + delta)
}
private fun contrast(x: Int, y: Int, minContrast: Int): Boolean {
val centerValue = get(x, y)
for (i in 8 downTo 0)
if (i != 4) {
val newx = x + i % 3 - 1
val newy = y + i / 3 - 1
if (newx >= 0 && newx < width && newy >= 0 && newy < height
&& Math.abs(get(newx, newy) - centerValue) >= minContrast)
return true
}
return false
}
}
internal fun readInputFromImage(filename: String): ArrayData {
val image = ImageIO.read(File(filename))
val w = image.width
val h = image.height
val rgbData = image.getRGB(0, 0, w, h, null, 0, w)
// flip y axis when reading image
val array = ArrayData(w, h)
for (y in 0..h - 1)
for (x in 0..w - 1) {
var rgb = rgbData[y * w + x]
rgb = ((rgb and 0xFF0000).ushr(16) * 0.30 + (rgb and 0xFF00).ushr(8) * 0.59 + (rgb and 0xFF) * 0.11).toInt()
array[x, h - 1 - y] = rgb
}
return array
}
fun main(args: Array<out String>) {
val inputData = readInputFromImage(args[0])
val minContrast = if (args.size >= 4) 64 else args[4].toInt()
inputData(args[2].toInt(), args[3].toInt(), minContrast).writeOutputImage(args[1])
}

View file

@ -0,0 +1,83 @@
import java.awt.image._
import java.io.File
import javax.imageio._
object HoughTransform extends App {
override def main(args: Array[String]) {
val inputData = readDataFromImage(args(0))
val minContrast = if (args.length >= 4) 64 else args(4).toInt
inputData(args(2).toInt, args(3).toInt, minContrast).writeOutputImage(args(1))
}
private def readDataFromImage(filename: String) = {
val image = ImageIO.read(new File(filename))
val width = image.getWidth
val height = image.getHeight
val rgbData = image.getRGB(0, 0, width, height, null, 0, width)
val arrayData = new ArrayData(width, height)
for (y <- 0 until height; x <- 0 until width) {
var rgb = rgbData(y * width + x)
rgb = (((rgb & 0xFF0000) >>> 16) * 0.30 + ((rgb & 0xFF00) >>> 8) * 0.59 +
(rgb & 0xFF) * 0.11).toInt
arrayData(x, height - 1 - y) = rgb
}
arrayData
}
}
class ArrayData(val width: Int, val height: Int) {
def update(x: Int, y: Int, value: Int) {
dataArray(x)(y) = value
}
def apply(thetaAxisSize: Int, rAxisSize: Int, minContrast: Int) = {
val maxRadius = Math.ceil(Math.hypot(width, height)).toInt
val halfRAxisSize = rAxisSize >>> 1
val outputData = new ArrayData(thetaAxisSize, rAxisSize)
val sinTable = Array.ofDim[Double](thetaAxisSize)
val cosTable = sinTable.clone()
for (theta <- thetaAxisSize - 1 until -1 by -1) {
val thetaRadians = theta * Math.PI / thetaAxisSize
sinTable(theta) = Math.sin(thetaRadians)
cosTable(theta) = Math.cos(thetaRadians)
}
for (y <- height - 1 until -1 by -1; x <- width - 1 until -1 by -1)
if (contrast(x, y, minContrast))
for (theta <- thetaAxisSize - 1 until -1 by -1) {
val r = cosTable(theta) * x + sinTable(theta) * y
val rScaled = Math.round(r * halfRAxisSize / maxRadius).toInt + halfRAxisSize
outputData.dataArray(theta)(rScaled) += 1
}
outputData
}
def writeOutputImage(filename: String) {
var max = Int.MinValue
for (y <- 0 until height; x <- 0 until width) {
val v = dataArray(x)(y)
if (v > max) max = v
}
val image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB)
for (y <- 0 until height; x <- 0 until width) {
val n = Math.min(Math.round(dataArray(x)(y) * 255.0 / max).toInt, 255)
image.setRGB(x, height - 1 - y, (n << 16) | (n << 8) | 0x90 | -0x01000000)
}
ImageIO.write(image, "PNG", new File(filename))
}
private def contrast(x: Int, y: Int, minContrast: Int): Boolean = {
val centerValue = dataArray(x)(y)
for (i <- 8 until -1 by -1 if i != 4) {
val newx = x + (i % 3) - 1
val newy = y + (i / 3) - 1
if (newx >= 0 && newx < width && newy >= 0 && newy < height &&
Math.abs(dataArray(newx)(newy) - centerValue) >= minContrast)
return true
}
false
}
private val dataArray = Array.ofDim[Int](width, height)
}