I would implement database.stateful and have a variable live above the start method.
Public Map<Id, Contact> conIdMap = new Map<Id,Contact>();
Then I would check to see if the contact has been processed by checking if the id exists as a key in the map:
// do something
For directly related contacts to Opportunities, you can access the Contact fields as well as the role within the subquery on OpportunityContactRoles when doing the SOQL on the Opportunity:
SELECT Id, Name,
(SELECT Id, ContactId, Contact.FirstName, Role FROM OpportunityContactRoles)
I don't think there's a direct way with one ...