Related
I would like to perform something similar to Vignette, but instead of Darken the edges, I would like to make a transparent gradient of the edges. I am not looking for a solution using widget.
Any idea how should I modify the code below? Or is it even the same thing at all?? I am sorry but I really badly need this.
Thanks a lot
Note: The code below is from the Image library
https://github.com/brendan-duncan/image
Image vignette(Image src, {num start = 0.3, num end = 0.75, num amount = 0.8}) {
final h = src.height - 1;
final w = src.width - 1;
final num invAmt = 1.0 - amount;
final p = src.getBytes();
for (var y = 0, i = 0; y <= h; ++y) {
final num dy = 0.5 - (y / h);
for (var x = 0; x <= w; ++x, i += 4) {
final num dx = 0.5 - (x / w);
num d = sqrt(dx * dx + dy * dy);
d = _smoothStep(end, start, d);
p[i] = clamp255((amount * p[i] * d + invAmt * p[i]).toInt());
p[i + 1] = clamp255((amount * p[i + 1] * d + invAmt * p[i + 1]).toInt());
p[i + 2] = clamp255((amount * p[i + 2] * d + invAmt * p[i + 2]).toInt());
}
}
return src;
}
num _smoothStep(num edge0, num edge1, num x) {
x = ((x - edge0) / (edge1 - edge0));
if (x < 0.0) {
x = 0.0;
}
if (x > 1.0) {
x = 1.0;
}
return x * x * (3.0 - 2.0 * x);
}
Solution
This code works without any widgets. Actually it doesn't use any of the flutter libraries and is solely based on dart and the image package you
introduced in your question.
The code contains comments which may not make a lot of sense until you read the explanation. The code is as following:
vignette.dart
import 'dart:isolate';
import 'dart:typed_data';
import 'package:image/image.dart';
import 'dart:math' as math;
class VignetteParam {
final Uint8List file;
final SendPort sendPort;
final double fraction;
VignetteParam(this.file, this.sendPort, this.fraction);
}
void decodeIsolate(VignetteParam param) {
Image image = decodeImage(param.file.buffer.asUint8List())!;
Image crop = copyCropCircle(image); // crop the image with builtin function
int r = crop.height~/2; // radius is half the height
int rs = r*r; // radius squared
int tr = (param.fraction * r).floor(); // we apply the fraction to the radius to get tr
int ors = (r-tr)*(r-tr); // ors from diagram
x: for (int x = -r; x <= r; x++) { // iterate across all columns of pixels after shifting x by -r
for (int y = -r; y <= r; y++) { // iterate across all rows of pixels after shifting y by -r
int pos = x*x + y*y; // which is (r')² (diagram)
if (pos <= rs) { // if we are inside the outer circle, then ...
if (pos > ors) { // if we are outside the inner circle, then ...
double f = (rs-pos) / (rs-ors); // calculate the fraction of the alpha value
int c = setAlpha(
crop.getPixelSafe(x+r, y+r),
(0xFF * f).floor()
); // calculate the new color by changing the alpha
crop.setPixelSafe(x+r, y+r, c); // set the new color
} else { // if we reach the inner circle from the top then jump down
y = y*-1;
}
} else { // if we are outside the outer circle and ...
if (y<0) { // above it and jump down to the outer circle
y = -(math.sin(math.acos(x/r)) * r).floor()-1;
}
else continue x; // or if beneath it then jump to the next column
}
}
}
param.sendPort.send(crop);
}
Future<Uint8List> getImage(Uint8List bytes, double radiusFraction) async {
var receivePort = ReceivePort();
await Isolate.spawn(
decodeIsolate,
VignetteParam(bytes, receivePort.sendPort, radiusFraction)
);
Image image = await receivePort.first;
return encodePng(image) as Uint8List;
}
main.dart (example app)
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_playground/vignette.dart';
import 'package:flutter/services.dart' show ByteData, rootBundle;
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Material App',
home: Scaffold(
appBar: AppBar(
title: const Text('Material App Bar'),
),
body: Center(
child: FutureBuilder<Uint8List>(
future: imageFuture(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.done:
return Image.memory(
snapshot.data!,
);
default:
return CircularProgressIndicator();
}
}
),
),
),
);
}
Future<Uint8List> imageFuture() async {
// Load your file here and call getImage
ByteData byteData = await rootBundle.load("assets/image.jpeg");
return getImage(byteData.buffer.asUint8List(), 0.3);
}
}
Explanation
The math behind this algorithm is very simple. It's only based on the equation of a circle. But first of all, have a look at this diagram:
Diagram
The diagram contains the square which is our image. Inside the square we can see the circle which is the visible part of the image. The opaque area is fully opaque while the transparent are gets more transparent (= less alpha) if we get closer to the outer circle. r (radius) is the radius of the outer circle, tr (transparent radius) is the part of the radius which is in the transparent area. That's why r-tr is the radius of the inner circle.
In order to apply this diagram to our needs we have to shift our x-Axis and y-Axis. The image package has a grid which starts with (0,0) at the top left corner. But we need (0,0) at the center of the image, hence we shift it by the radius r. If you look close you may notice that our diagram doesn't look as usual. The y-Axis usually goes up, but in our case it really doesn't matter and makes things easier.
Calculation of the position
We need to iterate across all pixels inside the transparent area and
change the alpha value. The equation of a circle, which is x'²+y'²=r'², helps us to figure out whether the pixel is in the transparent area. (x',y') is the coordinate of the pixel and r' is the distance between the pixel and the origin. Is the distance behind the inner circle and before the outer circle, then r' <= r and r' > r-tr must hold. In order to avoid calculating the square root, we instead compare the squared values, so pos <= rs and pos > ors must hold. (Look at the diagram to see what pos, rs, and ors are.)
Changing the alpha value
The pixel is inside the transparent area. How do we change the alpha value? We need a linear gradient transparency, so we calculate the distance between the actual position pos and the outer circle, which is rs-pos. The longer the distance, the more alpha we need for this pixel. The total distance is rs-ors, so we calculate (rs-pos) / (rs-ors) to get the fraction of our alpha value f. Finally we change our alpha value of this pixel with f.
Optimization
This algorithm actually does the whole job. But we can optimize it. I wrote that we have to iterate across all pixels inside the transparent area. So, we don't need the pixels outside of the outer circle and inside the inner circle, as their alpha value don't change. But we have two for loops iterating through all pixels from left to right and from top to bottom. Well, when we reach the inner circle from outside, we can jump down by negating the y-Position. Hence, we set y = y*-1; if we are inside the outer circle (pos <= rs) but not outside the inner circle (pos <= ors) anymore.
What if we are above the outer circle (pos > rs)? Well then we can jump to the outer circle by calculating the y-Position with sine and arccosine. I won't go much into detail here, but if you want further explanation, let me know by commenting below. The if (y<0) just determines if we are above the outer circle (y is negative) or beneath it. If above then jump down, if beneath jump to the next column of pixels. Hence we 'continue' the x for loop.
Here you go - the widgetless approach based on my previous answer:
Canvas(PictureRecorder()).drawImage(
your_image, //here is the image you want to change
Offset.zero,// the offset from the corner of the canvas
Paint()
..shader = const RadialGradient(
radius: needed_radius, // the radius of the result gradient - it should depend on the image dimens
colors: [Colors.black, Colors.transparent],
).createShader(
Rect.fromLTRB(0, 0, your_image_width, your_image_height), // the portion of your image that should be influenced by the shader - in this case, I use the whole image.
)
..blendMode = BlendMode.dstIn); // for the black color of the gradient to be masking one
I will add it to the previous answer, also
Given that you want "transparency", you need the alpha channel. The code you provide seems to only have 3 bytes per pixel, so only RGB, without alpha channel.
A solution may be:
Modify the code such that it has alpha channel, i.e. 4 bytes per pixel.
Instead of modifying the RGB to make it darker, i.e. p[i] = ..., p[i+1] = ..., p[i+2] = ..., leave RGB unchanged, and modifying the alpha channel to make alpha smaller. For example, say, p[i+3]=... (suppose you are RGBA format instead of ARGB).
I am trying to find a way to break the split the lines of text in a scanned document that has been adaptive thresholded. Right now, I am storing the pixel values of the document as unsigned ints from 0 to 255, and I am taking the average of the pixels in each line, and I split the lines into ranges based on whether the average of the pixels values is larger than 250, and then I take the median of each range of lines for which this holds. However, this methods sometimes fails, as there can be black splotches on the image.
Is there a more noise-resistant way to do this task?
EDIT: Here is some code. "warped" is the name of the original image, "cuts" is where I want to split the image.
warped = threshold_adaptive(warped, 250, offset = 10)
warped = warped.astype("uint8") * 255
# get areas where we can split image on whitespace to make OCR more accurate
color_level = np.array([np.sum(line) / len(line) for line in warped])
cuts = []
i = 0
while(i < len(color_level)):
if color_level[i] > 250:
begin = i
while(color_level[i] > 250):
i += 1
cuts.append((i + begin)/2) # middle of the whitespace region
else:
i += 1
EDIT 2: Sample image added
From your input image, you need to make text as white, and background as black
You need then to compute the rotation angle of your bill. A simple approach is to find the minAreaRect of all white points (findNonZero), and you get:
Then you can rotate your bill, so that text is horizontal:
Now you can compute horizontal projection (reduce). You can take the average value in each line. Apply a threshold th on the histogram to account for some noise in the image (here I used 0, i.e. no noise). Lines with only background will have a value >0, text lines will have value 0 in the histogram. Then take the average bin coordinate of each continuous sequence of white bins in the histogram. That will be the y coordinate of your lines:
Here the code. It's in C++, but since most of the work is with OpenCV functions, it should be easy convertible to Python. At least, you can use this as a reference:
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
// Read image
Mat3b img = imread("path_to_image");
// Binarize image. Text is white, background is black
Mat1b bin;
cvtColor(img, bin, COLOR_BGR2GRAY);
bin = bin < 200;
// Find all white pixels
vector<Point> pts;
findNonZero(bin, pts);
// Get rotated rect of white pixels
RotatedRect box = minAreaRect(pts);
if (box.size.width > box.size.height)
{
swap(box.size.width, box.size.height);
box.angle += 90.f;
}
Point2f vertices[4];
box.points(vertices);
for (int i = 0; i < 4; ++i)
{
line(img, vertices[i], vertices[(i + 1) % 4], Scalar(0, 255, 0));
}
// Rotate the image according to the found angle
Mat1b rotated;
Mat M = getRotationMatrix2D(box.center, box.angle, 1.0);
warpAffine(bin, rotated, M, bin.size());
// Compute horizontal projections
Mat1f horProj;
reduce(rotated, horProj, 1, CV_REDUCE_AVG);
// Remove noise in histogram. White bins identify space lines, black bins identify text lines
float th = 0;
Mat1b hist = horProj <= th;
// Get mean coordinate of white white pixels groups
vector<int> ycoords;
int y = 0;
int count = 0;
bool isSpace = false;
for (int i = 0; i < rotated.rows; ++i)
{
if (!isSpace)
{
if (hist(i))
{
isSpace = true;
count = 1;
y = i;
}
}
else
{
if (!hist(i))
{
isSpace = false;
ycoords.push_back(y / count);
}
else
{
y += i;
count++;
}
}
}
// Draw line as final result
Mat3b result;
cvtColor(rotated, result, COLOR_GRAY2BGR);
for (int i = 0; i < ycoords.size(); ++i)
{
line(result, Point(0, ycoords[i]), Point(result.cols, ycoords[i]), Scalar(0, 255, 0));
}
return 0;
}
Basic steps as #Miki,
read the source
threshed
find minAreaRect
warp by the rotated matrix
find and draw upper and lower bounds
While code in Python:
#!/usr/bin/python3
# 2018.01.16 01:11:49 CST
# 2018.01.16 01:55:01 CST
import cv2
import numpy as np
## (1) read
img = cv2.imread("img02.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
## (2) threshold
th, threshed = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV|cv2.THRESH_OTSU)
## (3) minAreaRect on the nozeros
pts = cv2.findNonZero(threshed)
ret = cv2.minAreaRect(pts)
(cx,cy), (w,h), ang = ret
if w>h:
w,h = h,w
ang += 90
## (4) Find rotated matrix, do rotation
M = cv2.getRotationMatrix2D((cx,cy), ang, 1.0)
rotated = cv2.warpAffine(threshed, M, (img.shape[1], img.shape[0]))
## (5) find and draw the upper and lower boundary of each lines
hist = cv2.reduce(rotated,1, cv2.REDUCE_AVG).reshape(-1)
th = 2
H,W = img.shape[:2]
uppers = [y for y in range(H-1) if hist[y]<=th and hist[y+1]>th]
lowers = [y for y in range(H-1) if hist[y]>th and hist[y+1]<=th]
rotated = cv2.cvtColor(rotated, cv2.COLOR_GRAY2BGR)
for y in uppers:
cv2.line(rotated, (0,y), (W, y), (255,0,0), 1)
for y in lowers:
cv2.line(rotated, (0,y), (W, y), (0,255,0), 1)
cv2.imwrite("result.png", rotated)
Finally result:
I am doing a background subtraction capture demo recently but I met with difficulties. I have already get the pixel of silhouette extraction and I intend to draw it into a buffer through createGraphics(). I set the new background is 100% transparent so that I could only get the foreground extraction. Then I use saveFrame() function in order to get png file of each frame. However, it doesn't work as I expected. I intend to get a series of png of the silhouette extraction
with 100% transparent background but now I only get the general png of frames from the camera feed. Is there anyone could help me to see what's the problem with this code? Thanks a lot in advance. Any help will be appreciated.
import processing.video.*;
Capture video;
PGraphics pg;
PImage backgroundImage;
float threshold = 30;
void setup() {
size(320, 240);
video = new Capture(this, width, height);
video.start();
backgroundImage = createImage(video.width, video.height, RGB);
pg = createGraphics(320, 240);
}
void captureEvent(Capture video) {
video.read();
}
void draw() {
pg.beginDraw();
loadPixels();
video.loadPixels();
backgroundImage.loadPixels();
image(video, 0, 0);
for (int x = 0; x < video.width; x++) {
for (int y = 0; y < video.height; y++) {
int loc = x + y * video.width;
color fgColor = video.pixels[loc];
color bgColor = backgroundImage.pixels[loc];
float r1 = red(fgColor); float g1 = green(fgColor); float b1 = blue(fgColor);
float r2 = red(bgColor); float g2 = green(bgColor); float b2 = blue(bgColor);
float diff = dist(r1, g1, b1, r2, g2, b2);
if (diff > threshold) {
pixels[loc] = fgColor;
} else {
pixels[loc] = color(0, 0);
}
}}
pg.updatePixels();
pg.endDraw();
saveFrame("line-######.png");
}
void mousePressed() {
backgroundImage.copy(video, 0, 0, video.width, video.height, 0, 0, video.width, video.height);
backgroundImage.updatePixels();
}
Re:
Then I use saveFrame() function in order to get png file of each frame. However, it doesn't work as I expected. I intend to get a series of png of the silhouette extraction with 100% transparent background but now I only get the general png of frames from the camera feed.
This won't work, because saveFrame() saves the canvas, and the canvas doesn't support transparency. For example, from the reference:
It is not possible to use the transparency alpha parameter with background colors on the main drawing surface. It can only be used along with a PGraphics object and createGraphics(). https://processing.org/reference/background_.html
If you want to dump a frame with transparency you need to use .save() to dump it directly from a PImage / PGraphics.
https://processing.org/reference/PImage_save_.html
If you need to clear your PImage / PGraphics and reuse it each frame, either use pg.clear() or pg.background(0,0,0,0) (set all pixels to transparent black).
I'm trying to properly detect the edges of a playing card that has been blurred, grayscaled and then thresholded. I thought having the sharp black and white contrast would make the edges quite easy to detect, but no joy so far. I'm starting with:
And using the Canny Edge Detector I wrote producing this:
The result of Sobel was basically the same. However using OpenCV's Canny Detection I could produce this:
The border being correctly fitted together is what I'm desperately needing to recreate in my own code, and I'm not committed to using any particular type of Edge Detection, I just need to find an algorithm that will give me the connected edge! My Canny code can be found here, and it is very much based from the LIRE code here. If anybody could help me go from the first image to the third I would be incredibly grateful! Any edge detection welcome!
Edit: Code for NMS:
//main program
for(int x = 1; x < width-1; x++)
{
for(int y = 1; y < height-1; y++)
{
if(src.getRaster().getPixel(x, y, tmp)[0] >= 250)
{
trackWeakOnes(x, y, src);
}
}
}
private static void trackWeakOnes(int x, int y, BufferedImage src)
{
for (int a = x - 1; a <= x + 1; a++)
{
for (int b = y - 1; b <= y + 1; b++)
{
if (checkWeak(a, b, src))
{
src.getRaster().setPixel(x, y, tmpMax);
trackWeakOnes(a, b, src);
}
}
}
}
private static boolean checkWeak(int x, int y, BufferedImage src)
{
return ((src.getRaster().getPixel(x, y, tmpPix)[0] > 0) &&
(src.getRaster().getPixel(x, y, tmpPix)[0] < 255));
}
tmpPix is an empty array to be filled, tmpMax is an array {255, 255, 255} to make edges white.
For this clean image, you don't need complex algorithms. A couple of simple filters will do the trick.
In matlab, the code looks like:
O=abs(filter2([-1 0 1],I))+abs(filter2([-1;0;1],I));
which means that for each pixel (x,y) you do:
output(x,y) = abs( I(x+1,y)-I(x-1,y) ) + abs( I(x,y+1) - I(x,y-1) );
I didn't read your code, but I observe a strange artifact: along the horizontal edges, the detected pixels come in isolated 8-connected triples. I would suspect a flaw in the non-maximum suppression logics. (In any case, there is an anisotropy somewhere.)
This said, edge detection on a binary image can be done by contour tracing.
I have the histogram for an image which i have calculated. I want to display this as an image so that I can actually see the histogram. I think my problem is to do with scaling although i am slightly confused over the co ordinate system starting with 0,0 in the top left as well.
int rows = channel.rows;
int cols = channel.cols;
int hist[256] = {0};
for(int i = 0; i<rows; i++)
{
for(int k = 0; k<cols; k++ )
{
int value = channel.at<cv::Vec3b>(i,k)[0];
hist[value] = hist[value] + 1;
}
}
Mat histPlot = cvCreateMat(256, 500,CV_8UC1);
for(int i = 0; i < 256; i++)
{
int mag = hist[i];
line(histPlot,Point(i,0),Point(i,mag),Scalar(255,0,0));
}
namedWindow("Hist",1);
imshow("Hist",histPlot);
This is my calculation for creating my histogram and displaying the result. If i do mag/100 in my second loop then i get some resemblance of a plot appearing (although upside down). I call this method whenever i adjust a value of my image, so the histogram should also change shape, which it doesn't appear to do. Any help in scaling the histogram and displaying it properly is appreciated.
please don't use cvCreateMat ( aka, the old c-api ), you also seem to have rows and cols wrong, additionally, if you want a color drawing, you need a color image as well, so make that:
Mat histPlot( 500, 256, CV_8UC3 );
image origin is top-left(0,0), so you've got to put y in reverse:
line(histPlot,Point(i,histPlot.rows-1),Point(i,histPlot.rows-1-mag/100),Scalar(255,0,0));