I am trying to do text segmentation. The attachment below is the results of it.
I manage to form lines to divide the image. However, I am stuck in splitting the image according to the lines that I'd found.
As labeled (red text) in the attached picture, I would like to split the image into 5 different images and I do not know where should I start. All the method I found only work for straight lines.
Header
Code - Source:
#include <opencv2/core/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
#include <ctype.h>
#include <fstream>
#define _USE_MATH_DEFINES
#include <math.h>
#define JC_VORONOI_IMPLEMENTATION
#include "jc_voronoi.h"
typedef struct compPoint
{
cv::Point pointer;
int siteNum, size ;
};
int maximumSize;
float average=0;
std::vector<compPoint> generatePoint(cv::Mat image);
void generateVoronoi(std::vector<cv::Point> points, int width, int height);
static inline jcv_point remap(const jcv_point* pt, const jcv_point* min, const jcv_point* max, const jcv_point* scale);
static void draw_line(int x0, int y0, int x1, int y1, unsigned char* image, int width, int height, int nchannels, unsigned char* color);
static void plot(int x, int y, unsigned char* image, int width, int height, int nchannels, unsigned char* color);
float areaDifference(int s1,int s2);
float areaDifference(int s1, int s2)
{
if (s1 > s2)
{
return s1 / s2;
}
else
{
return s2 / s1;
}
}
std::vector<compPoint> generatePoint(cv::Mat image)
{
cv::Mat grayscale, binary;
cv::cvtColor(image, grayscale, cv::COLOR_BGR2GRAY);
cv::threshold(grayscale, binary, 190, 255, 1);
std::vector<std::vector<cv::Point> > contours;
std::vector<cv::Vec4i> hierarchy;
cv::findContours(binary, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_NONE, cv::Point(0, 0));
std::vector<compPoint> extractedPoint;
cv::Mat drawing = cv::Mat::zeros(binary.size(), CV_8UC3);
cv::Scalar color = cv::Scalar(255, 255, 255);
maximumSize = cv::contourArea(contours[0]);
int skip = 0;
for (int i = 0; i < contours.size(); i++)
{
int jumpPoint = contours[i].size() / (contours[i].size() * 0.12);
bool isInner = false;
cv::Vec4i currentHierarchy = hierarchy[i];
if (contours[i].size() <= 20) //Remove small component
continue;
for (int g = 0; g < contours[i].size(); g = g + jumpPoint) //Sample point from connected component
{
compPoint temp;
temp.pointer = contours[i].at(g);
line(drawing, contours[i].at(g), contours[i].at(g), color, 1, 8, 0);
if (currentHierarchy.val[3] != -1)
{
int currentIndex = currentHierarchy.val[3];
while (hierarchy[currentIndex].val[3] != -1)
{
currentIndex = hierarchy[currentIndex].val[3];
}
temp.siteNum = currentIndex;
temp.size = cv::contourArea(contours[currentIndex]);
isInner = true;
}
else
{
temp.siteNum = i;
temp.size = cv::contourArea(contours[i]);
if (cv::contourArea(contours[i])>maximumSize)
{
maximumSize = cv::contourArea(contours[i]);
}
}
extractedPoint.push_back(temp);
}
if (isInner == false)
{
average = average + cv::contourArea(contours[i]);
skip++;
}
}
average = average/skip;
return extractedPoint;
}
static inline jcv_point remap(const jcv_point* pt, const jcv_point* min, const jcv_point* max, const jcv_point* scale)
{
jcv_point p;
p.x = (pt->x - min->x) / (max->x - min->x) * scale->x;
p.y = (pt->y - min->y) / (max->y - min->y) * scale->y;
return p;
}
static void plot(int x, int y, unsigned char* image, int width, int height, int nchannels, unsigned char* color)
{
if (x < 0 || y < 0 || x >(width - 1) || y >(height - 1))
return;
int index = y * width * nchannels + x * nchannels;
for (int i = 0; i < nchannels; ++i)
{
image[index + i] = color[i];
}
}
static void draw_line(int x0, int y0, int x1, int y1, unsigned char* image, int width, int height, int nchannels, unsigned char* color)
{
int dx = abs(x1 - x0), sx = x0<x1 ? 1 : -1;
int dy = -abs(y1 - y0), sy = y0<y1 ? 1 : -1;
int err = dx + dy, e2; // error value e_xy
for (;;)
{ // loop
plot(x0, y0, image, width, height, nchannels, color);
if (x0 == x1 && y0 == y1) break;
e2 = 2 * err;
if (e2 >= dy) { err += dy; x0 += sx; } // e_xy+e_x > 0
if (e2 <= dx) { err += dx; y0 += sy; } // e_xy+e_y < 0
}
}
void generateVoronoi(std::vector<compPoint> points, int width, int height)
{
int size = points.size();
jcv_point* voronoiPoint = (jcv_point*)malloc(sizeof(jcv_point) * (size_t)size);
for (int i = 0; i < size; i++)
{
voronoiPoint[i].x = (float)points[i].pointer.x;
voronoiPoint[i].y = (float)points[i].pointer.y;
voronoiPoint[i].site = points[i].siteNum;
voronoiPoint[i].totalPoint = points[i].size;
}
jcv_rect* rect = 0;
size_t imagesize = (size_t)(width*height * 3);
unsigned char* image = (unsigned char*)malloc(imagesize);
unsigned char* image2 = (unsigned char*)malloc(imagesize);
memset(image, 0, imagesize);
unsigned char color_pt[] = { 255, 255, 255 };
unsigned char color_line[] = { 220, 220, 220 };
jcv_diagram diagram;
jcv_point dimensions;
dimensions.x = (jcv_real)width;
dimensions.y = (jcv_real)height;
memset(&diagram, 0, sizeof(jcv_diagram));
jcv_diagram_generate(size, (const jcv_point*)voronoiPoint, rect, &diagram);
//Edge
const jcv_edge* edge = jcv_diagram_get_edges(&diagram);
std::vector<filtered_edge> filteredEdge;
float min_x = 0.0, min_y = 0.0;
while (edge) //Remove edge from the same connected component
{
jcv_point p0 = edge->pos[0];
jcv_point p1 = edge->pos[1];
if (edge->sites[0]->p.site != edge->sites[1]->p.site)
{
filteredEdge.push_back(jcv_save_edge(edge));
min_x = min_x + abs(edge->sites[0]->p.x - edge->sites[1]->p.x);
min_y = min_y + abs(edge->sites[0]->p.y - edge->sites[1]->p.y);
}
edge = edge->next;
}
min_x = min_x / filteredEdge.size();
min_y = min_y / filteredEdge.size();
std::vector<filtered_edge> selectedEdge;
for (int i = 0; i < filteredEdge.size(); i++)
{
jcv_point p0 = remap(&filteredEdge.at(i).pos[0], &diagram.min, &diagram.max, &dimensions);
jcv_point p1 = remap(&filteredEdge.at(i).pos[1], &diagram.min, &diagram.max, &dimensions);
float site_x = abs(filteredEdge.at(i).sites[0]->p.x - filteredEdge.at(i).sites[1]->p.x);
float site_y = abs(filteredEdge.at(i).sites[0]->p.y - filteredEdge.at(i).sites[1]->p.y);
float x_difference = abs(filteredEdge.at(i).pos[0].x- filteredEdge.at(i).pos[1].x);
float y_difference = abs(filteredEdge.at(i).pos[0].y - filteredEdge.at(i).pos[1].y);
float areaDiff = areaDifference(filteredEdge.at(i).sites[0]->p.totalPoint, filteredEdge.at(i).sites[1]->p.totalPoint);
if (p0.x - p1.x == 0 && p0.y - p1.y == 0.0) //Remove short edges
continue;
if (areaDiff > 20) //Keep edge between small(text) and big(image) component
{
float difference = abs(filteredEdge.at(i).sites[0]->p.totalPoint - filteredEdge.at(i).sites[1]->p.totalPoint);
if (difference > average*4 )
{
unsigned char color_line2[] = { 0, 220, 220 };
selectedEdge.push_back(filteredEdge.at(i));
draw_line((int)p0.x, (int)p0.y, (int)p1.x, (int)p1.y, image, width, height, 3, color_line2);
continue;
}
}
if (x_difference > y_difference) //Remove edge between close component
{
if (site_y > min_y*1.6)
{
unsigned char color_line2[] = { 220, 0, 220 };
selectedEdge.push_back(filteredEdge.at(i));
draw_line((int)p0.x, (int)p0.y, (int)p1.x, (int)p1.y, image, width, height, 3, color_line2);
}
}
else
{
if (site_x > min_x*2.5)
{
unsigned char color_line2[] = { 220, 220, 0 };
selectedEdge.push_back(filteredEdge.at(i));
draw_line((int)p0.x, (int)p0.y, (int)p1.x, (int)p1.y, image, width, height, 3, color_line2);
}
}
}
jcv_diagram_free(&diagram);
for (int i = 0; i < size; ++i)
{
jcv_point p = remap(&voronoiPoint[i], &diagram.min, &diagram.max, &dimensions);
plot((int)p.x, (int)p.y, image, width, height, 3, color_pt);
}
free(voronoiPoint);
cv::Mat segmentedImg = cv::Mat(height, width, CV_8UC3, image);
cv::imshow("Testing", segmentedImg);
cv::waitKey(0);
free(image);
}
int main()
{
cv::Mat image, skewCorrected;
image = cv::imread("C:\\figure5.PNG");
if (!image.data)
{
std::cout << "Error" << std::endl;
system("PAUSE");
return 0;
}
std::vector<compPoint> points = generatePoint(image);
int width = image.size().width, height = image.size().height;
generateVoronoi(points, width, height);
cv::waitKey(0);
}
Input image:
I don't understand many things in your code so I just appended some lines to do what you want.
1 - Create a Mat of zeros to draw the lines (CV_8U)
Mat dst = cv::Mat(height, width, CV_8U, cvScalar(0.));
2 - Draw the lines (using your points)
line( dst, Point((int)p0.x, (int)p0.y), Point((int)p1.x, (int)p1.y), Scalar( 255, 255, 255 ), 1, 8);
3 - Close the "holes" between the lines (CLOSE morphology operation)
int morph_size = 20; // adjust this values to your image
Mat element = getStructuringElement( MORPH_RECT, Size( 2*morph_size + 1, 2*morph_size+1 ), Point( morph_size, morph_size ) );
// Apply the CLOSE morphology operation
morphologyEx( dst, closed, MORPH_CLOSE, element );
4 - Flood fill to a mask (= "painting" the splitted areas)
// iterate through the points
for (int i = 0; i < closed.rows; i++ ) {
for (int j = 0; j < closed.cols; j++) {
// if point is not "painted" yet
if (closed.at<uchar>(i, j) == 0) {
// copy Mat before Flood fill
Mat previous_closed = closed.clone();
// Flood fill that seed point ("paint" that area)
floodFill(closed, Point(j, i), 255);
// Get mask with the "painted" area
Mat mask = closed - previous_closed;
/// Copy from segmentedImg using the mask
Mat outputMat;
segmentedImg.copyTo(outputMat, mask);
cv::imshow("Closed lines", closed);
imshow("Splitted Area", outputMat);
waitKey(0);
break;
}
}
}
Area 1:
Area 2:
Area 3:
... And so on, for the 5 areas, that loop basically keeps on painting the "black areas" in white and creating mats given the difference before and after each flood fill.
Full code (your code + this lines):
#include <opencv2/core/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/opencv.hpp>
using namespace cv;
#define JC_VORONOI_IMPLEMENTATION
#include "jc_voronoi.h"
typedef struct compPoint
{
cv::Point pointer;
int siteNum, size ;
};
int maximumSize;
float average=0;
std::vector<compPoint> generatePoint(cv::Mat image);
void generateVoronoi(std::vector<cv::Point> points, int width, int height);
static inline jcv_point remap(const jcv_point* pt, const jcv_point* min, const jcv_point* max, const jcv_point* scale);
static void draw_line(int x0, int y0, int x1, int y1, unsigned char* image, int width, int height, int nchannels, unsigned char* color);
static void plot(int x, int y, unsigned char* image, int width, int height, int nchannels, unsigned char* color);
float areaDifference(int s1,int s2);
float areaDifference(int s1, int s2)
{
if (s1 > s2)
{
return s1 / s2;
}
else
{
return s2 / s1;
}
}
std::vector<compPoint> generatePoint(cv::Mat image)
{
cv::Mat grayscale, binary;
cv::cvtColor(image, grayscale, cv::COLOR_BGR2GRAY);
cv::threshold(grayscale, binary, 190, 255, 1);
std::vector<std::vector<cv::Point> > contours;
std::vector<cv::Vec4i> hierarchy;
cv::findContours(binary, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_NONE, cv::Point(0, 0));
std::vector<compPoint> extractedPoint;
cv::Mat drawing = cv::Mat::zeros(binary.size(), CV_8UC3);
cv::Scalar color = cv::Scalar(255, 255, 255);
maximumSize = cv::contourArea(contours[0]);
int skip = 0;
for (int i = 0; i < contours.size(); i++)
{
int jumpPoint = contours[i].size() / (contours[i].size() * 0.12);
bool isInner = false;
cv::Vec4i currentHierarchy = hierarchy[i];
if (contours[i].size() <= 20) //Remove small component
continue;
for (int g = 0; g < contours[i].size(); g = g + jumpPoint) //Sample point from connected component
{
compPoint temp;
temp.pointer = contours[i].at(g);
line(drawing, contours[i].at(g), contours[i].at(g), color, 1, 8, 0);
if (currentHierarchy.val[3] != -1)
{
int currentIndex = currentHierarchy.val[3];
while (hierarchy[currentIndex].val[3] != -1)
{
currentIndex = hierarchy[currentIndex].val[3];
}
temp.siteNum = currentIndex;
temp.size = cv::contourArea(contours[currentIndex]);
isInner = true;
}
else
{
temp.siteNum = i;
temp.size = cv::contourArea(contours[i]);
if (cv::contourArea(contours[i])>maximumSize)
{
maximumSize = cv::contourArea(contours[i]);
}
}
extractedPoint.push_back(temp);
}
if (isInner == false)
{
average = average + cv::contourArea(contours[i]);
skip++;
}
}
average = average/skip;
return extractedPoint;
}
static inline jcv_point remap(const jcv_point* pt, const jcv_point* min, const jcv_point* max, const jcv_point* scale)
{
jcv_point p;
p.x = (pt->x - min->x) / (max->x - min->x) * scale->x;
p.y = (pt->y - min->y) / (max->y - min->y) * scale->y;
return p;
}
static void plot(int x, int y, unsigned char* image, int width, int height, int nchannels, unsigned char* color)
{
if (x < 0 || y < 0 || x >(width - 1) || y >(height - 1))
return;
int index = y * width * nchannels + x * nchannels;
for (int i = 0; i < nchannels; ++i)
{
image[index + i] = color[i];
}
}
static void draw_line(int x0, int y0, int x1, int y1, unsigned char* image, int width, int height, int nchannels, unsigned char* color)
{
int dx = abs(x1 - x0), sx = x0<x1 ? 1 : -1;
int dy = -abs(y1 - y0), sy = y0<y1 ? 1 : -1;
int err = dx + dy, e2; // error value e_xy
for (;;)
{ // loop
plot(x0, y0, image, width, height, nchannels, color);
if (x0 == x1 && y0 == y1) break;
e2 = 2 * err;
if (e2 >= dy) { err += dy; x0 += sx; } // e_xy+e_x > 0
if (e2 <= dx) { err += dx; y0 += sy; } // e_xy+e_y < 0
}
}
void generateVoronoi(std::vector<compPoint> points, int width, int height)
{
/// 1 - Create Mat of zeros to draw the lines
Mat dst = cv::Mat(height,width, CV_8U, cvScalar(0.));
int size = points.size();
jcv_point* voronoiPoint = (jcv_point*)malloc(sizeof(jcv_point) * (size_t)size);
for (int i = 0; i < size; i++)
{
voronoiPoint[i].x = (float)points[i].pointer.x;
voronoiPoint[i].y = (float)points[i].pointer.y;
voronoiPoint[i].site = points[i].siteNum;
voronoiPoint[i].totalPoint = points[i].size;
}
jcv_rect* rect = 0;
size_t imagesize = (size_t)(width*height * 3);
unsigned char* image = (unsigned char*)malloc(imagesize);
memset(image, 0, imagesize);
unsigned char color_pt[] = { 255, 255, 255 };
jcv_diagram diagram;
jcv_point dimensions;
dimensions.x = (jcv_real)width;
dimensions.y = (jcv_real)height;
memset(&diagram, 0, sizeof(jcv_diagram));
jcv_diagram_generate(size, (const jcv_point*)voronoiPoint, rect, &diagram);
//Edge
const jcv_edge* edge = jcv_diagram_get_edges(&diagram);
std::vector<filtered_edge> filteredEdge;
float min_x = 0.0, min_y = 0.0;
while (edge) //Remove edge from the same connected component
{
jcv_point p0 = edge->pos[0];
jcv_point p1 = edge->pos[1];
if (edge->sites[0]->p.site != edge->sites[1]->p.site)
{
filteredEdge.push_back(jcv_save_edge(edge));
min_x = min_x + abs(edge->sites[0]->p.x - edge->sites[1]->p.x);
min_y = min_y + abs(edge->sites[0]->p.y - edge->sites[1]->p.y);
}
edge = edge->next;
}
min_x = min_x / filteredEdge.size();
min_y = min_y / filteredEdge.size();
std::vector<filtered_edge> selectedEdge;
for (int i = 0; i < filteredEdge.size(); i++)
{
jcv_point p0 = remap(&filteredEdge.at(i).pos[0], &diagram.min, &diagram.max, &dimensions);
jcv_point p1 = remap(&filteredEdge.at(i).pos[1], &diagram.min, &diagram.max, &dimensions);
float site_x = abs(filteredEdge.at(i).sites[0]->p.x - filteredEdge.at(i).sites[1]->p.x);
float site_y = abs(filteredEdge.at(i).sites[0]->p.y - filteredEdge.at(i).sites[1]->p.y);
float x_difference = abs(filteredEdge.at(i).pos[0].x- filteredEdge.at(i).pos[1].x);
float y_difference = abs(filteredEdge.at(i).pos[0].y - filteredEdge.at(i).pos[1].y);
float areaDiff = areaDifference(filteredEdge.at(i).sites[0]->p.totalPoint, filteredEdge.at(i).sites[1]->p.totalPoint);
if (p0.x - p1.x == 0 && p0.y - p1.y == 0.0) //Remove short edges
continue;
/// 2 - Draw lines
if (areaDiff > 20) //Keep edge between small(text) and big(image) component
{
float difference = abs(filteredEdge.at(i).sites[0]->p.totalPoint - filteredEdge.at(i).sites[1]->p.totalPoint);
if (difference > average*4 )
{
unsigned char color_line2[] = { 0, 220, 220 };
selectedEdge.push_back(filteredEdge.at(i));
draw_line((int)p0.x, (int)p0.y, (int)p1.x, (int)p1.y, image, width, height, 3, color_line2);
line( dst, Point((int)p0.x, (int)p0.y), Point((int)p1.x, (int)p1.y), Scalar( 255, 255, 255 ), 1, 8);
continue;
}
}
if (x_difference > y_difference) //Remove edge between close component
{
if (site_y > min_y*1.6)
{
unsigned char color_line2[] = { 220, 0, 220 };
selectedEdge.push_back(filteredEdge.at(i));
draw_line((int)p0.x, (int)p0.y, (int)p1.x, (int)p1.y, image, width, height, 3, color_line2);
line( dst, Point((int)p0.x, (int)p0.y), Point((int)p1.x, (int)p1.y), Scalar( 255, 255, 255 ), 1, 8);
}
}
else
{
if (site_x > min_x*2.5)
{
unsigned char color_line2[] = { 220, 220, 0 };
selectedEdge.push_back(filteredEdge.at(i));
draw_line((int)p0.x, (int)p0.y, (int)p1.x, (int)p1.y, image, width, height, 3, color_line2);
line( dst, Point((int)p0.x, (int)p0.y), Point((int)p1.x, (int)p1.y), Scalar( 255, 255, 255 ), 1, 8);
}
}
}
jcv_diagram_free(&diagram);
for (int i = 0; i < size; ++i)
{
jcv_point p = remap(&voronoiPoint[i], &diagram.min, &diagram.max, &dimensions);
plot((int)p.x, (int)p.y, image, width, height, 3, color_pt);
}
free(voronoiPoint);
cv::Mat segmentedImg = cv::Mat(height, width, CV_8UC3, image);
cv::imshow("Testing", segmentedImg);
cv::imshow("Lines", dst);
/// New code:
Mat closed = dst.clone();
/// 3 - Close the "holes" between the lines
int morph_size = 20; // adjust this values to your image
Mat element = getStructuringElement( MORPH_RECT, Size( 2*morph_size + 1, 2*morph_size+1 ), Point( morph_size, morph_size ) );
// Apply the CLOSE morphology operation
morphologyEx( dst, closed, MORPH_CLOSE, element );
imshow("Closed lines", closed);
waitKey(0);
/// 4 - Flood fill to a mask
// iterate through the points
for (int i = 0; i < closed.rows; i++ ) {
for (int j = 0; j < closed.cols; j++) {
// if point is not "painted" yet
if (closed.at<uchar>(i, j) == 0) {
// copy Mat before Flood fill
Mat previous_closed = closed.clone();
// Flood fill that seed point ("paint" that area)
floodFill(closed, Point(j, i), 255);
// Get mask with the "painted" area
Mat mask = closed - previous_closed;
/// 5 - Copy from segmentedImg using the mask
Mat outputMat;
segmentedImg.copyTo(outputMat, mask);
cv::imshow("Closed lines", closed);
imshow("Splitted Area", outputMat);
waitKey(0);
break;
}
}
}
free(image);
}
int main()
{
cv::Mat image, skewCorrected;
image = cv::imread("/home/tribta/Downloads/HI2IT.png");
if (!image.data)
{
std::cout << "Error" << std::endl;
system("PAUSE");
return 0;
}
std::vector<compPoint> points = generatePoint(image);
int width = image.size().width, height = image.size().height;
generateVoronoi(points, width, height);
cv::waitKey(0);
}
I'm trying to Shear an image along the X-axis using OpenCV to load the image, and the following algorithm to shear the image: x′=x+y·Bx, but for some reason, I end up with the following shear:
My source code looks like this:
#include "stdafx.h"
#include "opencv2\opencv.hpp"
using namespace std;
using namespace cv;
int main()
{
Mat src = imread("B2DBy.jpg", 1);
if (src.empty())
cout << "Error: Loading image" << endl;
int r1, c1; // tranformed point
int rows, cols; // original image rows and columns
rows = src.rows;
cols = src.cols;
float Bx = 2; // amount of shearing in x-axis
float By = 0; // amount of shearing in y-axis
int maxXOffset = abs(cols * Bx);
int maxYOffset = abs(rows * By);
Mat out = Mat::ones(src.rows + maxYOffset, src.cols + maxXOffset, src.type()); // create output image to be the same as the source
for (int r = 0; r < out.rows; r++) // loop through the image
{
for (int c = 0; c < out.cols; c++)
{
r1 = r + c * By - maxYOffset; // map old point to new
c1 = r * Bx + c - maxXOffset;
if (r1 >= 0 && r1 <= out.rows && c1 >= 0 && c1 <= out.cols) // check if the point is within the boundaries
{
out.at<uchar>(r, c) = src.at<uchar>(r1, c1); // set value
}
}
}
namedWindow("Source image", CV_WINDOW_AUTOSIZE);
namedWindow("Rotated image", CV_WINDOW_AUTOSIZE);
imshow("Source image", src);
imshow("Rotated image", out);
waitKey(0);
return 0;
}
EDIT
Fixed it myself.
Didn't need to substract the offset. Heres the updated source code:
Mat forward(Mat img) {
Mat umg = img;
int y1, x1; // tranformed point
int rows, cols; // original image rows and columns
rows = umg.rows;
cols = umg.cols;
float Bx = 0.7; // amount of shearing in x-axis
float By = 0; // amount of shearing in y-axis
int maxXOffset = abs(rows * Bx);
int maxYOffset = abs(cols * By);
Mat out = Mat::ones(rows + maxYOffset, cols + maxXOffset, umg.type()); // create output image to be the same as the source
for (int y = 0; y < rows; y++) // loop through the image
{
for (int x = 0; x < cols; x++)
{
y1 = y + x * By; // map old point to new
x1 = y * Bx + x;
out.at<uchar>(y1, x1) = umg.at<uchar>(y, x); // set value
}
}
return out;
}
Mat backwards(Mat img) {
Mat umg = img;
int y1, x1; // tranformed point
int rows, cols; // original image rows and columns
rows = umg.rows;
cols = umg.cols;
float Bx = 0.7; // amount of shearing in x-axis
float By = 0; // amount of shearing in y-axis
int maxXOffset = abs(rows * Bx);
int maxYOffset = abs(cols * By);
Mat out = Mat::ones(rows + maxYOffset, cols + maxXOffset, umg.type()); // create output image to be the same as the source
for (int y = 0; y < rows; y++) // loop through the image
{
for (int x = 0; x < cols; x++)
{
//y1 = y + x * By; // map old point to new
//x1 = y * Bx + x;
y1 = (1 / (1 - Bx*By)) * (y + x * By);
x1 = (1 / (1 - Bx*By)) * (y * Bx + x);
out.at<uchar>(y1, x1) = umg.at<uchar>(y, x); // set value
}
}
return out;
}
int main()
{
Mat src = imread("B2DBy.jpg", 0);
if (src.empty())
cout << "Error: Loading image" << endl;
Mat forwards = forward(src);
Mat back = backwards(src);
namedWindow("Source image", CV_WINDOW_NORMAL);
imshow("Source image", src);
imshow("back", back);
imshow("forward image", forwards);
waitKey(0);
return 0;
}
I found some time to work on this.
Now I understand what you tried to achieve with the offset computation, but I'm not sure whether yours is correct.
Just change all the cv::Vec3b to unsigned char or uchar and load as grayscale, if wanted.
Please try this code and maybe you'll find your error:
// no interpolation yet
// cv::Vec3b only
cv::Mat shear(const cv::Mat & input, float Bx, float By)
{
if (Bx*By == 1)
{
throw("Shearing: Bx*By==1 is forbidden");
}
if (input.type() != CV_8UC3) return cv::Mat();
// shearing:
// x'=x+y·Bx
// y'=y+x*By
// shear the extreme positions to find out new image size:
std::vector<cv::Point2f> extremePoints;
extremePoints.push_back(cv::Point2f(0, 0));
extremePoints.push_back(cv::Point2f(input.cols, 0));
extremePoints.push_back(cv::Point2f(input.cols, input.rows));
extremePoints.push_back(cv::Point2f(0, input.rows));
for (unsigned int i = 0; i < extremePoints.size(); ++i)
{
cv::Point2f & pt = extremePoints[i];
pt = cv::Point2f(pt.x + pt.y*Bx, pt.y + pt.x*By);
}
cv::Rect offsets = cv::boundingRect(extremePoints);
cv::Point2f offset = -offsets.tl();
cv::Size resultSize = offsets.size();
cv::Mat shearedImage = cv::Mat::zeros(resultSize, input.type()); // every pixel here is implicitely shifted by "offset"
// perform the shearing by back-transformation
for (int j = 0; j < shearedImage.rows; ++j)
{
for (int i = 0; i < shearedImage.cols; ++i)
{
cv::Point2f pp(i, j);
pp = pp - offset; // go back to original coordinate system
// go back to original pixel:
// x'=x+y·Bx
// y'=y+x*By
// y = y'-x*By
// x = x' -(y'-x*By)*Bx
// x = +x*By*Bx - y'*Bx +x'
// x*(1-By*Bx) = -y'*Bx +x'
// x = (-y'*Bx +x')/(1-By*Bx)
cv::Point2f p;
p.x = (-pp.y*Bx + pp.x) / (1 - By*Bx);
p.y = pp.y - p.x*By;
if ((p.x >= 0 && p.x < input.cols) && (p.y >= 0 && p.y < input.rows))
{
// TODO: interpolate, if wanted (p is floating point precision and can be placed between two pixels)!
shearedImage.at<cv::Vec3b>(j, i) = input.at<cv::Vec3b>(p);
}
}
}
return shearedImage;
}
int main(int argc, char* argv[])
{
cv::Mat input = cv::imread("C:/StackOverflow/Input/Lenna.png");
cv::Mat output = shear(input, 0.7, 0);
//cv::Mat output = shear(input, -0.7, 0);
//cv::Mat output = shear(input, 0, 0.7);
cv::imshow("input", input);
cv::imshow("output", output);
cv::waitKey(0);
return 0;
}
Giving me these outputs for the 3 sample lines:
I'm having problems with the DFT function in OpenCV 2.4.8 for c++.
I used an image of a 10 phases sinus curve to compare the old cvDFT() with the newer c++ function DFT() (one dimensional DFT row-wise).
The old version gives me logical results: very high peak at pixel 0 and 10, the rest being almost 0.
The new version gives me strange results with peaks all over the spectrum.
Here is my code:
#include "stdafx.h"
#include <opencv2\core\core_c.h>
#include <opencv2\core\core.hpp>
#include <opencv2\imgproc\imgproc_c.h>
#include <opencv2\imgproc\imgproc.hpp>
#include <opencv2\highgui\highgui_c.h>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\legacy\compat.hpp>
using namespace cv;
void OldMakeDFT(Mat original, double* result)
{
const int width = original.cols;
const int height = 1;
IplImage* fftBlock = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 1);
IplImage* imgReal = cvCreateImage(cvSize(width, height), IPL_DEPTH_32F, 1);
IplImage* imgImag = cvCreateImage(cvSize(width, height), IPL_DEPTH_32F, 1);
IplImage* imgDFT = cvCreateImage(cvSize(width, height), IPL_DEPTH_32F, 2);
Rect roi(0, 0, width, 1);
Mat image_roi = original(roi);
fftBlock->imageData = (char*)image_roi.data;
//cvSaveImage("C:/fftBlock1.png", fftBlock);
cvConvert(fftBlock, imgReal);
cvMerge(imgReal, imgImag, NULL, NULL, imgDFT);
cvDFT(imgDFT, imgDFT, (CV_DXT_FORWARD | CV_DXT_ROWS));
cvSplit(imgDFT, imgReal, imgImag, NULL, NULL);
double re,imag;
for (int i = 0; i < width; i++)
{
re = ((float*)imgReal->imageData)[i];
imag = ((float*)imgImag->imageData)[i];
result[i] = re * re + imag * imag;
}
cvReleaseImage(&imgReal);
cvReleaseImage(&imgImag);
cvReleaseImage(&imgDFT);
cvReleaseImage(&fftBlock);
}
void MakeDFT(Mat original, double* result)
{
const int width = original.cols;
const int height = 1;
Mat fftBlock(1,width, CV_8UC1);
Rect roi(0, 0, width, height);
Mat image_roi = original(roi);
image_roi.copyTo(fftBlock);
//imwrite("C:/fftBlock2.png", fftBlock);
Mat planes[] = {Mat_<float>(fftBlock), Mat::zeros(fftBlock.size(), CV_32F)};
Mat complexI;
merge(planes, 2, complexI);
dft(complexI, complexI, DFT_ROWS); //also tried with DFT_COMPLEX_OUTPUT | DFT_ROWS
split(complexI, planes);
double re, imag;
for (int i = 0; i < width; i++)
{
re = (float)planes[0].data[i];
imag = (float)planes[1].data[i];
result[i] = re * re + imag * imag;
}
}
bool SinusFFTTest()
{
const int size = 1024;
Mat sinTest(size,size,CV_8UC1, Scalar(0));
const int n_sin_curves = 10;
double deg_step = (double)n_sin_curves*360/size;
for (int j = 0; j < size; j++)
{
for (int i = 0; i <size; i++)
{
sinTest.data[j*size+i] = 127.5 * sin(i*deg_step*CV_PI/180) + 127.5;
}
}
double* result1 = new double[size];
double* result2 = new double[size];
OldMakeDFT(sinTest,result1);
MakeDFT(sinTest,result2);
bool identical = true;
for (int i = 0; i < size; i++)
{
if (abs(result1[i] - result2[i]) > 1000)
{
identical = false;
break;
}
}
delete[] result1;
delete[] result2;
return identical;
}
int _tmain(int argc, _TCHAR* argv[])
{
if (SinusFFTTest())
{
printf("identical");
}
else
{
printf("different");
}
getchar();
return 0;
}
Could someone explain the difference?
imgReal - is not filled with zeroes by default.
The bug in in the MakeDFT() function:
re = (float)planes[0].data[i];
imag = (float)planes[1].data[i];
data[i]'s type is uchar, and its conversion to float is not right.
The fix:
re = planes[0].at<float>(0,i);
imag = planes[1].at<float>(0,i);
After this change, the old and the new DFT versions gives the same results. Or, you can use cv::magnitude() instead of calculating the sum of squares of re and imag:
Mat magn;
magnitude(planes[0], planes[1], magn);
for (int i = 0; i < width; i++)
result[i] = pow(magn.at<float>(0,i),2);
This gives also the same result as the old cvDFT.