Angelo Marcelino Cordeiro
Página desenvolvida como decumentação para os projetos desenvolvidos durante a disciplina DCA0445 - PROCESSAMENTO DIGITAL DE IMAGENS.
Link para o tutorial de opencv: Introdução ao processamento digital de imagens com OpenCV.
1. Primeira unidade
Neste tópico estão descritos as atividades realizadas referentes a primeira unidade da disciplina DCA0445.
Para a compilação dos códigos referentes à esta unidade, é necessário possuir, no mesmo diretório do arquivo .cpp
o arquivo Makefile, e executar os seguintes comandos no seu console:
$ make <nome_arquivo>
$ ./<nome_arquivo> <caminho_para_imagem>
1.1. Regiões
Este código tem a função de um programa que recebe do usuário dois pontos P1 e P2 e verifica se os pontos fornecidos estão dentro da imagem. Em seguida delimita-se a região definida pelo retângulo de vértices opostos definidos pelos pontos P1 e P2 na qual é exibido o negativo da imagem efeito esse alcançado subtraindo 255 do valor de cada pixel que compõe o retângulo.
O código fonte regions.cpp está está descrito abaixo:
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
struct ponto
{
int x, y;
};
int main(int argc, char **argv)
{
srand(time(NULL));
Mat image;
ponto p1, p2;
Vec3b val;
image = imread(argv[1], CV_LOAD_IMAGE_GRAYSCALE);
if (!image.data)
cout << "A imagem nao foi carregada corretamente" << endl;
namedWindow("janela", WINDOW_AUTOSIZE);
cout << "Insira os pontos do retângulo dentro dos limites da imagem ("
<< image.rows << "," << image.cols << "): ";
cin >> p1.x >> p1.y >> p2.x >> p2.y;
imshow("janela", image);
imwrite("grayscale.png", image);
waitKey();
for (int i = p1.x; i < p2.x; i++)
{
for (int j = p1.y; j < p2.y; j++)
{
image.at<uchar>(i, j) = 255 - image.at<uchar>(i, j);
}
}
imshow("janela", image);
imwrite("negativo_grayscale.png", image);
waitKey();
image = imread(argv[1], CV_LOAD_IMAGE_COLOR);
if (!image.data)
cout << "A imagem nao foi carregada corretamente" << endl;
imshow("janela", image);
waitKey();
val[0] = 255; //B
val[1] = 255; //G
val[2] = 255; //R
for (int i = p1.x; i < p2.x; i++)
{
for (int j = p1.y; j < p2.y; j++)
{
image.at<Vec3b>(i, j) = val - image.at<Vec3b>(i, j);
}
}
imshow("janela", image);
imwrite("negativo_color.png", image);
waitKey();
return 0;
}
A seguir estão as imagens de entrada do código acima:
.
.
1.2. Trocar Regiões
A atividade propõe a construção de um programa que recebe uma imagem, determina e troca os quadrantes diagonalmente numa imagem de saída. Para resolver este problema foi seguido o seguinte algoritmo:
-
Obtêm-se os quadrantes da imagem defindo o tamanho da grid como metade da altura e largura da imagem, e utilizando o construtor da classe
Rect
, seleciona um retângulo da matriz igual ao quadrante selecionado. -
Com o construtor da classe
Mat
, copia-se cada quadrante para uma matriz separada. -
Por fim, concatenam-se os quadrantes utilizando as funções
vconcat()
ehconcat()
na imagem, como descreve o código trocaregioes.cpp abaixo:
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main(int argc, char **argv)
{
Mat image, A, B, C, D, Vab, Vcd;
Rect rect;
image = imread(argv[1], CV_LOAD_IMAGE_COLOR);
if (!image.data)
{
cout << "A imagem nao foi carregada corretamente" << endl;
return -1;
}
namedWindow("janela", WINDOW_AUTOSIZE);
imshow("janela", image);
cout << image.rows << " " << image.cols << endl;
waitKey();
rect = Rect(Point(0, 0), Point(image.rows / 2, image.cols / 2));
A = Mat(image, rect);
rect = Rect(Point(0, image.cols / 2), Point(image.rows / 2, image.cols));
B = Mat(image, rect);
rect = Rect(Point(image.rows / 2, 0), Point(image.rows, image.cols / 2));
C = Mat(image, rect);
rect = Rect(Point(image.rows / 2, image.cols / 2), Point(image.rows, image.cols));
D = Mat(image, rect);
vconcat(B, A, Vab);
vconcat(D, C, Vcd);
hconcat(Vcd, Vab, image);
imshow("janela", image);
imwrite("regioestrocadas.png", image);
waitKey();
return 0;
}
A seguir estão exemplos de imagens de entrada e saída do código acima:
.
1.3. Preenchendo Regiões
Este algoritmo tem a função de contar as bolhas que possuem buracos e as que não possuem buracos, eliminando as que tocam as bordas.
Primeiramente, a exclusão dos objetos que tocam as bordas foi realizada percorrendo-se as primeiras e últimas linhas e colunas da imagem a procura de objetos e, em seguida, preecehendo-os com o valor de fundo usando a função floodFill()
.
Assim, pega-se o ponto (0.0) da imagem e aplica o algoritmo floodfill neste ponto com a cor de background que será utilizada para preencher todo o fundo da imagem. Como é suposto que os objetos são aréas com cor em grayscale de 255 e os buracos são da mesma cor do fundo antigo, 0, resta então contabilizar quantos objetos existem e quantos deles possuem buracos, seguindo o algoritmo descrito abaixo:
-
Caso encotre um buraco com um objeto associado, contabilize o objeto com buraco e preencha o objeto com a nova cor de fundo, por fim, preecher o buraco achado com a cor de fundo.
-
Caso encontre um buraco sem objeto associado, preencha-o com a nova cor de fundo.
-
Por fim, contabilize os objetos restantes, preenchendo-os com a cor de fundo.
Este processo pode ser visualizado nas figuras abaixo:
.
O código implementado, rotulacao.cpp, juntamente da execução do programa estão apresentados abaixo:
$ make rotulacao
$ ./rotulacao bolhas.png
nholes = 7
nobjects = 14
#include <iostream>
#include <stdio.h>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
#define BKGROUND 140
int main(int argc, char **argv)
{
Mat image, mask;
int width, height;
int nobjects, nholes;
nobjects = nholes = 0;
CvPoint p;
image = imread(argv[1], CV_LOAD_IMAGE_GRAYSCALE);
if (!image.data)
{
std::cout << "imagem nao carregou corretamente\n";
return (-1);
}
width = image.size().width;
height = image.size().height;
p.x = 0;
p.y = 0;
// busca objetos na borda de cima e de baixo
for (int col = 0; col < width; col++)
{
if (image.at<uchar>(0, col) == 255)
{
// achou um objeto
p.x = col;
p.y = 0;
floodFill(image, p, 0);
}
if (image.at<uchar>(height - 1, col) == 255)
{
// achou um objeto
p.x = col;
p.y = height - 1;
floodFill(image, p, 0);
}
}
// busca objetos na bordas da esq e dir
for (int row = 0; row < height; row++)
{
if (image.at<uchar>(row, 0) == 255)
{
// achou um objeto
p.x = 0;
p.y = row;
floodFill(image, p, 0);
}
if (image.at<uchar>(row, width - 1) == 255)
{
// achou um objeto
p.x = width - 1;
p.y = row;
floodFill(image, p, 0);
}
}
imshow("image", image);
imwrite("bolhas_borda.png", image);
waitKey();
// mudar o fundo
floodFill(image, cvPoint(0, 0), BKGROUND);
imshow("image", image);
imwrite("bolhas_fundo.png", image);
waitKey();
// encontrando buracos
for (int row = 0; row < height; row++)
{
for (int col = 0; col < width; col++)
{
if (image.at<uchar>(row, col) == 0)
{
if (image.at<uchar>(row, col - 1) == 255)
{
// achou um buraco
nholes++;
p.x = col - 1;
p.y = row;
floodFill(image, p, BKGROUND);
}
p.x = col;
p.y = row;
floodFill(image, p, BKGROUND);
}
}
}
imshow("image", image);
imwrite("bolhas_semburacos.png", image);
waitKey();
// encontrando regioes
for (int row = 1; row < height; row++)
{
for (int col = 1; col < width; col++)
{
if (image.at<uchar>(row, col) == 255)
{
nobjects++;
p.x = col;
p.y = row;
floodFill(image, p, BKGROUND);
}
}
}
imshow("image", image);
imwrite("bolhas_result.png", image);
cout << "nholes = " << nholes << endl;
cout << "nobjects = " << nobjects << endl;
waitKey();
return 0;
}
1.4. Equalização de Histogramas
"Um histograma é uma contagem de dados onde se organiza as ocorrências por faixas de valores predefinidos. Em se tratando de imagens digitais em tons de cinza, por exemplo, costuma-se associar um histograma com a contagem de ocorrências de cada um dos possíveis tons em uma imagem. A grosso modo, o histograma oferece uma estimativa da probabilidade de ocorrência dos tons de cinza na imagem."[1]
O que a equalização de um histograma faz é, basicamente, estender o intervalo dessas probabilidades.
Para tanto, o código equalize.cpp foi criado para implementar este conceito para uma imagem em escala de cinza, mostrando tanto seu histograma e imagem em modo normal quanto ambos equalizados.
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int argc, char **argv)
{
int nbins = 64;
float range[] = {0, 256};
const float *histrange = {range};
bool uniform = true;
bool acummulate = false;
Mat hist, histEq;
VideoCapture cap;
cap.open(0);
if (!cap.isOpened())
{
cout << "Erro ao acessar a camera!\n";
return -1;
}
// Calcula a largura e altura dos framas capturados
int width = cap.get(CV_CAP_PROP_FRAME_WIDTH),
height = cap.get(CV_CAP_PROP_FRAME_HEIGHT);
cout << "Width: " << width << endl
<< "Height: " << height << endl;
while (true)
{
Mat img, imgEq;
cap >> img;
// Inverte a imagem
flip(img, img, 1);
// Tranforma a imagem capturada para grayscale
cvtColor(img, img, CV_BGR2GRAY);
// Equaliza o histograma
equalizeHist(img, imgEq);
// Calcula o histograma
calcHist(&img, 1, 0, Mat(), hist, 1,
&nbins, &histrange,
uniform, acummulate);
calcHist(&imgEq, 1, 0, Mat(), histEq, 1,
&nbins, &histrange,
uniform, acummulate);
int histw = nbins, histh = nbins / 2;
Mat histImg(histh, histw, CV_8UC1, Scalar(0, 0, 0));
Mat histImgEq(histh, histw, CV_8UC1, Scalar(0, 0, 0));
// Normaliza o histograma para aparecer nas imagens
normalize(hist, hist, 0, histImg.rows, NORM_MINMAX, -1, Mat());
normalize(histEq, histEq, 0, histImgEq.rows, NORM_MINMAX, -1, Mat());
for (int i = 0; i < nbins; i++)
{
line(histImg,
Point(i, histh),
Point(i, histh - cvRound(hist.at<float>(i))),
Scalar(255, 255, 255), 1, 8, 0);
line(histImgEq,
Point(i, histh),
Point(i, histh - cvRound(histEq.at<float>(i))),
Scalar(255, 255, 255), 1, 8, 0);
}
histImg.copyTo(img(Rect(0, 0, nbins, histh)));
histImgEq.copyTo(imgEq(Rect(0, 0, nbins, histh)));
imshow("Equalized Histogram", imgEq);
imshow("Normal", img);
if (waitKey(30) == 'a')
break;
}
return 0;
}
Estre progrmam tem saídas como as seguintes:
.
1.5. Detector de Movimento
Com o conceito de histograma mais fixado, foi implementado, então um programa detector de movimento utilizando esses conhecimentos.
Como o histograma é um tipo de descrição para uma imagem, a sua comparação com outras pode identificar a similaridade entre elas. Seguindo esse conceito, o código motiondetector.cpp implementa essa comparação entre frames de um vídeo e caso esta comparação seja maior que um limite pré-definido, um quadro branco aparecerá no vídeo e o console mostrará a quantidade de frames nos quais algum movimento foi detectado e qual foi o valor de comparação entre o retorno da a função compareHist(currentHist, previousHist, 1)
subtraída do limite definido.
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int argc, char **argv)
{
VideoCapture cap;
cap.open(0);
if (!cap.isOpened())
{
cout << "Erro ao acessar a camera!\n";
return -1;
}
// Calcula a largura e altura dos framas capturados
int width = cap.get(CV_CAP_PROP_FRAME_WIDTH),
height = cap.get(CV_CAP_PROP_FRAME_HEIGHT);
// Treshold de detecção de movimento
double threshold = 30;
int a = 0;
cout << "Width: " << width << endl
<< "Height: " << height << endl
<< "Threshold =" << threshold << endl;
// Exibição do histograma
int nbins = 64;
float range[] = {0, 256};
const float *histRange = {range};
Mat previousHist, currentHist;
// Exibição da imagem
Mat img;
// Primeiro quadro a ser guardado
cap >> img;
flip(img, img, 1);
cvtColor(img, img, CV_BGR2GRAY);
calcHist(&img, 1, 0, Mat(), previousHist, 1, &nbins, &histRange);
normalize(previousHist, previousHist, 0, previousHist.rows, NORM_MINMAX, -1, Mat());
while (true)
{
cap >> img;
// Inverte a imagem
flip(img, img, 1);
// Tranforma a imagem capturada para grayscale
cvtColor(img, img, CV_BGR2GRAY);
// Calcula o histograma
calcHist(&img, 1, 0, Mat(), currentHist, 1, &nbins, &histRange);
// Normaliza o histograma
normalize(currentHist, currentHist, 0, currentHist.rows, NORM_MINMAX, -1, Mat());
// Comparação de histogramas
double correlation = compareHist(currentHist, previousHist, 1);
if ((correlation - threshold) > threshold)
{
Mat flag(64, 64, CV_8UC1, 255);
flag.copyTo(img(Rect(0, 0, 64, 64)));
cout << a << " " << (correlation - threshold) << endl;
a++;
}
currentHist.copyTo(previousHist);
imshow("Motion Detector", img);
if (waitKey(30) == 'a')
break;
}
return 0;
}
1.6. Filtragem espacial I
O seguinte programa laplgauss.cpp permite que seja calculado o Laplaciano do Gaussiano (LoG) das imagens capturadas. Para tanto, foi criada uma opção que permite a aplicação da máscara gaussiana, seguida da laplaciana (3x3). Além disto, foi adicionado também a máscara do LoG (5x5). Para efeito de comparação, o programa implementado dispõe das opções:
-
Laplaciano 3x3
-
Laplaciano do Gaussiano 3x3
-
Laplaciano do Gaussiano 5x5
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
void printmask(Mat& m) {
for (int i = 0; i < m.size().height; i++) {
for (int j = 0; j < m.size().width; j++) {
cout << m.at<float>(i, j) << ",";
}
cout << endl;
}
}
void menu() {
cout << "\nPressione a tecla correspondente para ativar o filtro: \n"
"a - imagem original\n"
"l - laplaciano 3x3 sem gauss\n"
"g - laplaciano do gaussiano 3x3\n"
"5 - laplaciano do gaussiano 5x5\n"
"esc - sair\n";
}
int main(int argvc, char** argv) {
VideoCapture video;
// Masks
Mat mgauss, mlaplacian, mlog, mask, mask1;
float id[] = {0, 0, 0, 0, 1, 0, 0, 0, 0};
float gauss[] = {1, 2, 1, 2, 4, 2, 1, 2, 1};
float laplacian[] = {0, -1, 0, -1, 4, -1, 0, -1, 0};
float lapofgau[] = {0, 0, -1, 0, 0, 0, -1, -2, -1, 0, -1, -2, 16,
-2, -1, 0, -1, -2, -1, 0, 0, 0, -1, 0, 0};
Mat cap, frame, frame32f, frameFiltered;
Mat result, result1;
double width, height;
mask = mask1 = Mat(3, 3, CV_32F, id);
mgauss = Mat(3, 3, CV_32F, gauss) / (float)(16);
mlaplacian = Mat(3, 3, CV_32F, laplacian);
mlog = Mat(5, 5, CV_32F, lapofgau);
video.open(0);
if (!video.isOpened()) return -1;
width = video.get(CV_CAP_PROP_FRAME_WIDTH);
height = video.get(CV_CAP_PROP_FRAME_HEIGHT);
cout << "Largura = " << width << "\n";
cout << "Altura = " << height << "\n";
namedWindow("filtroespacial", 1);
menu();
while (true) {
video >> cap;
cvtColor(cap, frame, CV_BGR2GRAY);
flip(frame, frame, 1);
imshow("original", frame);
frame.convertTo(frame32f, CV_32F);
filter2D(frame32f, frameFiltered, -1, mask, Point(-1, -1), 0,
BORDER_DEFAULT);
filter2D(frameFiltered, frameFiltered, -1, mask1, Point(-1, -1), 0,
BORDER_DEFAULT);
frameFiltered.convertTo(result, CV_8U);
imshow("filtroespacial", result);
char key = (char)waitKey(30);
if (key == 27) break;
switch (key) {
case 'a':
menu();
mask = mask1 = Mat(3, 3, CV_32F, id);
printmask(mask);
break;
case 'l':
menu();
mask = mlaplacian;
mask1 = Mat(3, 3, CV_32F, id);
printmask(mask);
break;
case 'g':
menu();
mask = mgauss;
printmask(mask);
mask1 = mlaplacian;
printmask(mask1);
break;
case '5':
menu();
mask = mgauss;
mask1 = mlog;
printmask(mask1);
break;
default:
break;
}
}
return 0;
}
Estre progrmam tem saídas como as seguintes:
.
É interessante notar que a máscara do LoG 5x5 produz uma definição de bordas bastante significativa em comparação com as máscaras 3x3, tanto que bordas suaves como as de uma tatuagem aparecem nítidamente nesta máscara.
1.6. Filtragem espacial II
A ativadade propõe a criação de uma interface que proporcione ao usuário aplicar o efeito Tilf shift em imgaens, para tal criada duas imagens a partir da imagem de entrada, uma usada apenas para a multiplicação ponderada e outra usada para armazenar o borramento da imagem de entrada. Ao final é feita a a multiplicação ponderada dos pixels seguindo o controle de desfoque feito manualmente a partir de sliders presentes na interface.
O seguinte programa tiltshift.cpp permite que seja calculado os pesos para que seja reproduzido o efeito de Tilt Shift.
#include <iostream>
#include <opencv2/opencv.hpp>
#include <math.h>
using namespace cv;
using namespace std;
// GLOBAL VARIABLES
int blurValue = 7 ;
int blur_slider = 0;
int blur_slider_max = 10;
int MAX = 100;
double ponderacao = 6;
int ponderacao_slider = 0;
int top_slider = 0;
int posicao_vertical = 0;
int tamanho_faixa = 20;
int altura_slider = 0;
Mat img1, img2, alpha, beta1;
int height;
char TrackbarName[50];
// FUNCTIONS
void refresh(){
Mat srcImg, blurImg, tiltshiftImg;
img1.convertTo(srcImg, CV_32FC3);
img2.convertTo(blurImg, CV_32FC3);
multiply(srcImg, alpha, srcImg);
multiply(blurImg, beta1, blurImg);
add(srcImg, blurImg, tiltshiftImg);
tiltshiftImg.convertTo(tiltshiftImg, CV_8UC3);
imshow("tiltshift", tiltshiftImg);
imwrite("tiltshift.jpg", tiltshiftImg);
}
void blur(int blurValue){
Mat aux, mask;
float media[] = {1,1,1,
1,1,1,
1,1,1};
// Creates the Mask matrix
mask = Mat(3, 3, CV_32F, media);
scaleAdd(mask, 1/9.0, Mat::zeros(3,3,CV_32F), mask);
// "img1" is used otherwise the blur will always increase
img1.convertTo(aux, CV_32F);
for (int i = 0; i < blurValue; i++) {
filter2D(aux, aux, aux.depth(), mask, Point(1, 1), 0);
}
aux=abs(aux);
aux.convertTo(img2, CV_8UC3);
}
void tiltShiftCalc() {
int l1 = -tamanho_faixa/2;
int l2 = -l1;
alpha = Mat::zeros(img1.rows, img1.cols, CV_32F);
beta1 = Mat::zeros(img1.rows, img1.cols, CV_32F);
int i, j;
for (i = 0; i < alpha.rows; i++) {
int x = i - (posicao_vertical + tamanho_faixa/2);
float alphaValue = 0.5f * (tanh((x - l1)/ponderacao)
- tanh((x - l2)/ponderacao));
for (j = 0; j < alpha.cols; j++) {
alpha.at<float>(i, j) = alphaValue;
beta1.at<float>(i, j) = 1 - alphaValue;
}
}
Mat auxA[] = {alpha, alpha, alpha};
Mat auxB[] = {beta1, beta1, beta1};
merge(auxA, 3, alpha);
merge(auxB, 3, beta1);
// imshow("alpha", alpha);
// imshow("beta", beta1);
refresh();
}
// TRACKBARS
void on_trackbar_blur(int, void*){
blurValue = blur_slider;
// Blurs image
blur(blurValue);
// Updates window
refresh();
}
void on_trackbar_ponderacao(int, void*){
ponderacao = (double) ponderacao_slider;
if (ponderacao < 1) {
ponderacao = 1;
}
tiltShiftCalc();
}
void on_trackbar_posicaoVertical(int, void*){
posicao_vertical = top_slider*height/MAX;
tiltShiftCalc();
}
void on_trackbar_altura_regiao(int, void*) {
tamanho_faixa = altura_slider*height/MAX;
if (tamanho_faixa == 0) {
tamanho_faixa = 1;
}
if (tamanho_faixa > height) {
tamanho_faixa = height;
}
tiltShiftCalc();
}
// MAIN
int main(int argvc, char** argv){
img1 = imread(argv[1], IMREAD_COLOR);
height = img1.size().height;
img2 = img1.clone();
img2.convertTo(img2, CV_8UC3);
blur(7);
namedWindow("tiltshift", 1);
// sprintf( TrackbarName, "Blur Value");
// createTrackbar( TrackbarName, "Blur",
// &blur_slider,
// blur_slider_max,
// on_trackbar_blur);
// on_trackbar_blur(blur_slider, 0);
sprintf( TrackbarName, "Posição Vertical");
createTrackbar( TrackbarName, "tiltshift",
&top_slider,
MAX,
on_trackbar_posicaoVertical );
sprintf( TrackbarName, "Altura da região");
createTrackbar( TrackbarName, "tiltshift",
&altura_slider,
MAX,
on_trackbar_altura_regiao);
sprintf( TrackbarName, "Ponderação");
createTrackbar( TrackbarName, "tiltshift",
&ponderacao_slider,
MAX,
on_trackbar_ponderacao);
tiltShiftCalc();
waitKey(0);
return 0;
}
Estre progrmam tem a execução como se segue:
.
O conceito de do programa tiltshift.cpp também pode ser aplicado à videos, assim o programa tiltshift.cpp foi criado, mudando somente a função main.cpp
:
// MAIN
int main(int argvc, char** argv){
VideoCapture video;
video.open(argv[1]);
if(!video.isOpened()){
cout << "Error opening video stream or file" << endl;
return -1;
}
height = video.get(CV_CAP_PROP_FRAME_HEIGHT);
cout << "Altura = " << height << "\n";
namedWindow("tiltshift", 1);
sprintf( TrackbarName, "Posição Vertical");
createTrackbar( TrackbarName, "tiltshift",
&top_slider,
MAX,
on_trackbar_posicaoVertical );
sprintf( TrackbarName, "Altura da região");
createTrackbar( TrackbarName, "tiltshift",
&altura_slider,
MAX,
on_trackbar_altura_regiao);
sprintf( TrackbarName, "Ponderação");
createTrackbar( TrackbarName, "tiltshift",
&ponderacao_slider,
MAX,
on_trackbar_ponderacao);
int frameCount = 0;
Mat discardFrame;
while(true){
video >> discardFrame;
if (discardFrame.empty()){
video.release();
video.open(argv[1]);
if(!video.isOpened()){
cout << "Error opening video stream or file" << endl;
return -1;
}
frameCount = 0;
continue;
}
frameCount++;
if(frameCount%2 != 0){
img1 = discardFrame.clone();
img2 = img1.clone();
blur(7);
tiltShiftCalc();
}
char key = (char)waitKey(30);
if (key == 27) break;
}
// When everything done, release the video capture object
video.release();
// Closes all the frames
destroyAllWindows();
return 0;
}
2. Segunda unidade
Neste tópico estão descritos as atividades realizadas referentes a segunda unidade da disciplina DCA0445.
Para a compilação dos códigos referentes à esta unidade, é necessário possuir, no mesmo diretório do arquivo .cpp
o arquivo Makefile, e executar os seguintes comandos no seu console:
$ make <nome_arquivo>
$ ./<nome_arquivo> <caminho_para_imagem>
2.1. Filtragem no domínio da Frequência
O exercíco propõe a criação de um programa que auxilie no melhoramento de imagens com iluminação irregular utilizando o filtro homomórfico, para tal foi criada uma interface com sliders para ajustar os paramentros do filtro e são mostradas em janelas diferentes a imagem com a aplicação do filtro, a imagem original e o filtro que está sendo aplicado.
O programa homomorfico.cpp, está descrito abaixo:
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
void deslocaDFT(Mat& image ){
Mat tmp, A, B, C, D;
image = image(Rect(0, 0, image.cols & -2, image.rows & -2));
int cx = image.cols/2;
int cy = image.rows/2;
A = image(Rect(0, 0, cx, cy));
B = image(Rect(cx, 0, cx, cy));
C = image(Rect(0, cy, cx, cy));
D = image(Rect(cx, cy, cx, cy));
A.copyTo(tmp); D.copyTo(A); tmp.copyTo(D);
C.copyTo(tmp); B.copyTo(C); tmp.copyTo(B);
}
int gammaL_slider = 3, gammaH_slider = 20, sharpC_slider = 1, cutoff_slider = 5;
const int gammaL_max = 10, gammaH_max = 50, sharpC_max = 100, cutoff_max = 200;
int gammaL, gammaH, sharpC, cutoff;
Mat image, imageFiltered,padded;
int dft_M, dft_N;
Mat homomorphicFilter(double gl, double gh, double c, double d0){
Mat filter = Mat(padded.size(), CV_32FC2, Scalar(0));
Mat tmp = Mat(dft_M, dft_N, CV_32F);
for(int i=0; i<dft_M; i++){
for(int j=0; j<dft_N; j++){
tmp.at<float> (i,j) = (gh - gl)*(1 - exp(-c*(( (i-dft_M/2)*(i-dft_M/2) + (j-dft_N/2)*(j-dft_N/2) ) / (d0*d0) ))) + gl;
}
}
Mat comps[]= {tmp,tmp};
imshow("Filter", tmp);
merge(comps, 2, filter);
return filter;
}
void applyFilter(void){
vector<Mat> planos; planos.clear();
Mat zeros = Mat_<float>::zeros(padded.size());
Mat realInput = Mat_<float>(padded);
Mat complex;
realInput += Scalar::all(1);
log(realInput,realInput);
planos.push_back(realInput);
planos.push_back(zeros);
merge(planos, complex);
dft(complex, complex);
deslocaDFT(complex);
resize(complex,complex,padded.size());
normalize(complex,complex,0,1,CV_MINMAX);
Mat filter = homomorphicFilter(gammaL,gammaH,sharpC,cutoff);
mulSpectrums(complex,filter,complex,0);
deslocaDFT(complex);
idft(complex, complex);
planos.clear();
split(complex, planos);
exp(planos[0],planos[0]);
normalize(planos[0], planos[0], 0, 1, CV_MINMAX);
imageFiltered = planos[0].clone();
}
void on_trackbar(int, void*){
gammaL = (double) gammaL_slider/10;
gammaH = (double) gammaH_slider/10;
sharpC = (double) sharpC_slider;
cutoff = (double) cutoff_slider;
applyFilter();
imshow("Homomorphic",imageFiltered);
}
int main(int argc, char** argv){
if(argc < 2) {
cout << "Argumentos faltando!\nUso: ./homorfico <caminho_para_imagem>\n";
return -1;
}
image = imread(argv[1], CV_LOAD_IMAGE_GRAYSCALE);
if(!image.data) {
cout<<"Erro ao abrir a imagem!\n";
return -1;
}
namedWindow("Homomorphic", WINDOW_KEEPRATIO);
namedWindow("Original", WINDOW_KEEPRATIO);
namedWindow("Filter", WINDOW_KEEPRATIO);
imshow("Original",image);
dft_M = getOptimalDFTSize(image.rows);
dft_N = getOptimalDFTSize(image.cols);
copyMakeBorder(image, padded, 0, dft_M - image.rows, 0, dft_N - image.cols, BORDER_CONSTANT, Scalar::all(0));
imageFiltered = padded.clone();
cout << "Original: " << image.rows << "x" << image.cols << endl;
cout << "Padded: " << padded.rows << "x" << padded.cols << endl;
char TrackbarName[50];
sprintf( TrackbarName, "Gamma L x %d", gammaL_max );
createTrackbar( TrackbarName, "Homomorphic", &gammaL_slider, gammaL_max, on_trackbar);
sprintf( TrackbarName, "Gamma H x %d", gammaH_max );
createTrackbar( TrackbarName, "Homomorphic", &gammaH_slider, gammaH_max, on_trackbar);
sprintf( TrackbarName, "C x %d", sharpC_max );
createTrackbar( TrackbarName, "Homomorphic", &sharpC_slider, sharpC_max, on_trackbar);
sprintf( TrackbarName, "Cutoff Frequency x %d", cutoff_max );
createTrackbar( TrackbarName, "Homomorphic", &cutoff_slider, cutoff_max, on_trackbar);
waitKey(0);
return 0;
}
.
2.2. Detecção de bordas com o algoritmo de Canny
O exercício solicita a implementação de um programa para usar as bordas produzidas pelo algoritmo de Canny para melhorar a qualidade da imagem pontilhista gerada, para tal utiliza-se a posição dos pixels de borda encontrados pelo algoritmo de Canny para desenhar pontos nos respectivos locais na imagem gerada através do programa cannyPoints.cpp:
#include <iostream>
#include <vector>
#include <opencv2/opencv.hpp>
#define RAIO 3
using namespace std;
using namespace cv;
int canny_slider = 15;
int canny_slider_max = 200;
char TrackbarName[50];
Mat image, edges, points;
void getPointillisticImage() {
// Cria variaveis para as dimensoes da imagem
int height = image.size().height, width = image.size().width;
// Cria as matrizes para as bordas da imagem e para a imagem de pontos
points = Mat(height, width, CV_8UC3, Scalar(255, 255, 255));
Canny(image, edges, canny_slider, 3 * canny_slider);
// Percorre a matriz com as bordas e preenche um vetor com as coordenadas
// dos pixels de valor 255
vector<vector<int>> edgesCoordinates;
for(int i = 0; i < height; i++) {
for(int j = 0; j < width; j++) {
if(edges.at<uchar>(i, j) == 255) {
edgesCoordinates.push_back({i, j});
}
}
}
// Embaralha as coordenas onde os circulos serão desenhados
// para aumentar a autenticidade do resultado
random_shuffle(edgesCoordinates.begin(), edgesCoordinates.end());
// Percorre as coordenadas e desenha em uma nova matrix os pontos
// com cor igual a cor da imagem na posição correspodente
for(auto coordinate : edgesCoordinates) {
Vec3b color = image.at<Vec3b>(coordinate[0], coordinate[1]);
circle(points, Point(coordinate[1], coordinate[0]), RAIO, color, -1, CV_AA);
}
}
void on_trackbar_canny(int, void*){
getPointillisticImage();
imshow("Canny Points", points);
}
int main(int argc, char**argv) {
if(argc < 2) {
cout << "Argumentos faltando!\nUso: ./cannyPoints <caminho_para_imagem>\n";
return -1;
}
// Carrega a imagem
image = imread(argv[1], CV_LOAD_IMAGE_COLOR);
if(!image.data) {
cout << "Error ao carregar a imagem!\n";
return -1;
}
sprintf(TrackbarName, "Threshold", canny_slider_max);
namedWindow("Canny Points", WINDOW_KEEPRATIO);
createTrackbar(
TrackbarName,
"Canny Points",
&canny_slider,
canny_slider_max,
on_trackbar_canny
);
on_trackbar_canny(canny_slider, 0);
waitKey(0);
imwrite("canny.png", points);
return 0;
}
As entradas e execução do programa estão exemplificadas a seguir: